├── .env.example.json
├── .gitignore
├── .gitmodules
├── .travis.yml
├── Dockerfile
├── LICENSE
├── README.md
├── app
├── cache
│ ├── pbs.pb.go
│ └── pbs.proto
├── console
│ ├── commands
│ │ ├── hello.go
│ │ └── kernel.go
│ └── schedule.go
├── events
│ ├── kernel.go
│ ├── protocol_buffers
│ │ ├── pbs.pb.go
│ │ └── pbs.proto
│ └── user_registered.go
├── http
│ ├── controllers
│ │ ├── login.go
│ │ ├── register.go
│ │ ├── user.go
│ │ ├── user_affiliation.go
│ │ └── webdav.go
│ ├── middleware
│ │ └── example.go
│ └── requests
│ │ ├── login.go
│ │ └── register.go
├── jobs
│ ├── example.go
│ ├── kernel.go
│ └── protocol_buffers
│ │ ├── pbs.pb.go
│ │ └── pbs.proto
├── listeners
│ ├── add_user_affiliation.go
│ └── kernel.go
├── logics
│ └── mindav
│ │ ├── driver
│ │ └── miniofs
│ │ │ ├── fileinfo.go
│ │ │ ├── interfaces.go
│ │ │ ├── minio.go
│ │ │ └── webdav_file.go
│ │ ├── filesystem.go
│ │ └── initialize.go
├── models
│ ├── failed_queue.go
│ ├── user.go
│ ├── user_affiliation.go
│ └── user_affiliation_test.go
└── policies
│ └── user_policy.go
├── artisan.go
├── bootstrap
└── app.go
├── config
├── app.go
├── auth.go
├── cache.go
├── database.go
├── kernel.go
├── queue.go
├── sentry.go
├── user_affiliation.go
└── webdav.go
├── database
├── migrations
│ ├── create_failed_queue_table_1556612225.go
│ ├── create_user_affiliation_table_1553678539.go
│ ├── create_user_table_1548750742.go
│ └── kernel.go
└── seeds
│ └── .gitkeep
├── docker-compose.yaml
├── go.mod
├── main.go
├── makefile
├── readme_assets
├── 37E56D20-FCA7-41FB-B8B2-3B5E390A6DBC.png
└── architecture.png
├── resources
├── lang
│ ├── en.go
│ ├── kernel.go
│ └── zh.go
└── views
│ ├── kernel.go
│ └── user_affiliation.nodes.go
└── routes
├── groups
├── auth.go
├── user.go
├── user_affiliation.go
└── webdav.go
├── provider.go
└── versions
└── v1.go
/.env.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "APP_NAME": "Mindav",
3 | "APP_ENV": "develop",
4 | "APP_DEBUG": true,
5 | "APP_PORT": 80,
6 | "APP_LOCALE": "en",
7 | "APP_KEY": "YOUR-APP-KEY",
8 |
9 | "DB_CONNECTION": "mysql",
10 | "DB_HOST": "127.0.0.1",
11 | "DB_PORT": "3306",
12 | "DB_DATABASE": "YOUR-DATABASE-NAME",
13 | "DB_USERNAME": "YOUR-DATABASE-USER",
14 | "DB_PASSWORD": "YOUR-DATABASE-USER-PASSWORD",
15 | "DB_PREFIX": "prefix_",
16 |
17 | "CACHE_DRIVER": "memory",
18 | "REDIS_HOST": "127.0.0.1",
19 | "REDIS_PORT": "6379",
20 | "REDIS_PASSWORD": "",
21 | "REDIS_DB": 0,
22 | "REDIS_CACHE_DB": 1,
23 |
24 | "AUTH_SIGN_KEY": "YOUR-AUTH-SIGN-KEY",
25 |
26 | "QUEUE_CONNECTION": "nsq",
27 | "QUEUE_NSQD_TCP_HOST": "127.0.0.1",
28 | "QUEUE_NSQD_TCP_PORT": "4150",
29 | "QUEUE_NSQLOOKUPD_HTTP_HOST": "http://127.0.0.1",
30 | "QUEUE_NSQLOOKUPD_HTTP_PORT": "4161",
31 | "QUEUE_MAX_IN_FLIGHT": "50",
32 |
33 | "WEBDAV_DRIVER": "minio",
34 | "WEBDAV_USER": "totoval",
35 | "WEBDAV_PASSWORD": "passw0rd",
36 | "MINIO_ENDPOINT": "minio:9000",
37 | "MINIO_ACCESS_KEY_ID": "minio",
38 | "MINIO_SECRET_ACCESS_KEY": "miniostorage",
39 | "MINIO_BUCKET": "mindav",
40 | "MINIO_USE_SSL": false,
41 | "MEMORY_UPLOAD_MODE": false
42 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | framework
3 | .env.json
4 | go.sum
5 | .env.home.json
6 | .env.local.json
7 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/totoval/mindav/fcd09af481cddcb02b97ea1b19ca3c723d03078d/.gitmodules
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | os:
3 | - linux
4 | - osx
5 | go:
6 | - master
7 | env:
8 | - GO111MODULE=on
9 | script:
10 | - go build -o ./builds/main_$TRAVIS_OS_NAME ./main.go
11 | #- go build -o ./builds/artisan_$TRAVIS_OS_NAME ./artisan.go
12 | deploy:
13 | provider: releases
14 | api_key:
15 | secure: pFtTcRuMBq6hK05WiG0jh6Q5ak8VD8awU2iQL6Wjvw+deRPeIR9OYpTgwh3h83ZWaRmq6iMMI2Rf0zMIi0iTvkYqqAPcOxudaxdZmHXxMdu8D+ScPF1rKxJmy7QKrShwZtjawY8wR8lNLRvxWWJA2L2Qsf5N6Px3QkmFSSyw0FKAt88sOJbYMK7/Ce4B1M6uGSPDxgmoab9jHpdkse3YfwknUj9w6RpdmE1H7RlmlB7OIe3icoTAyCbO7Pq+WjdCttd8ehWOr7cG/l8GSnd/wiivZwo1D+m9l74dDMDvvm5W4Gstc3FRUg7Xx60nmhSntgwV5dtvEPtxZNqdf6HEnk6O13SUVTEjGHFlKdJvJuCPMaht5BDWDxEsM2NW8Z8AoqEo8MeL3PUN2EqvvZRbWZIJgx1zxkRcYDNPgJHmRknmrt3SbaMKAVZdUXywwR9WCgpQ/HSVlbePGGxELGbthlkmb7MktUsRbV1FE/5UanixgOYuzOKfHg3Vbojc4JnrwtIoqtomfzjPWZ4+hACude5kciM4QiABymDDe8KY7QJP+6QoAPqWaj5dGSXEUs0+U5r7K0+5V/UfCRp6U7Qd5YDI2v7VxSdt6Gx18tKQn05lS54h7Z4mFtvWEjptT7451esRf1hBNYFZGdaJbVKTO3edYBSXYWl8moOXuN/LhCU=
16 | file_glob: true
17 | file: "./builds/*"
18 | skip_cleanup: true
19 | on:
20 | repo: totoval/mindav
21 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ############################
2 | # STEP 1 build executable binary
3 | ############################
4 | FROM golang:1.12-stretch AS builder
5 | COPY . /app/src/
6 | #ENV GOPROXY=https://mirrors.aliyun.com/goproxy/
7 | ENV CGO_ENABLED=0
8 | ENV GOOS=linux
9 | ENV GOARCH=amd64
10 | #COPY $GOPATH /go
11 | WORKDIR /app/src/
12 |
13 | RUN go build -o /app/src/builds/server /app/src/main.go
14 | #RUN go build -o /app/src/builds/artisan /app/src/artisan.go
15 |
16 | ############################
17 | # STEP 2 build a small server image
18 | ############################
19 | FROM scratch
20 | # Copy .env.json
21 | COPY --from=builder /app/src/.env.example.json /mindav/.env.json
22 | # Copy our static executable.
23 | COPY --from=builder /app/src/builds/server /mindav/server
24 | #COPY --from=builder /app/src/builds/artisan /bin/artisan
25 | WORKDIR /mindav/
26 | # Run the server binary.
27 | ENTRYPOINT ["/mindav/server"]
28 | EXPOSE 80
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2019 Totoval
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MinDAV
2 | 
3 | 
4 | [](https://goreportcard.com/report/github.com/totoval/mindav)
5 | 
6 | 
7 | 
8 | 
9 |
10 | ## About MinDAV
11 | MinDAV is a self-hosted file backup server which bridges WebDAV protocol with Minio.
12 |
13 | **WebDAV ❤️ Minio**
14 |
15 | ## Why them?
16 | ### WebDAV
17 |
18 | > Web Distributed Authoring and Versioning (WebDAV) is an extension of the Hypertext Transfer Protocol (HTTP) that allows clients to perform remote Web content authoring operations.
19 |
20 | There're many cloud storages that support WebDAV protocol, such as **dropbox**, **owncloud**, **nextcloud**, etc.
21 |
22 | ***WebDAV provides a simple port for your files.***
23 |
24 | ### Minio
25 | > The 100% Open Source, Enterprise-Grade, Amazon S3 Compatible Object Storage
26 |
27 | ***Minio is [reliable](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for your files.***
28 |
29 | ## Architecture
30 |
31 |
32 |
33 | ## One Click Start
34 | ```bash
35 | git clone git@github.com:totoval/mindav.git
36 | cd mindav
37 | cp .env.example.json .env.json
38 | docker-compose up -d
39 | ```
40 | Now you can connect the MinDAV by using your favorite WebDAV clients, such as [Cyberduck](http://cyberduck.io):
41 |
42 |
43 | ## Getting Started
44 | > Assumed that you already have your [Minio](https://github.com/minio/minio) server running. Or [Quick Run Minio Server](#quick-run-minio-server)
45 | * `cp .env.example.json .env.json`
46 | * Config your Minio in your `.env.json` file
47 | ```json
48 | {
49 | "WEBDAV_DRIVER": "minio",
50 | "WEBDAV_USER": "totoval",
51 | "WEBDAV_PASSWORD": "passw0rd",
52 | "MINIO_ENDPOINT": "play.min.io:9000",
53 | "MINIO_ACCESS_KEY_ID": "access_key_id",
54 | "MINIO_SECRET_ACCESS_KEY": "secret_access_key",
55 | "MINIO_BUCKET": "bucket_name",
56 | "MINIO_USE_SSL": false,
57 | "MEMORY_UPLOAD_MODE": false
58 | }
59 | ```
60 | * Run `go run main.go` or the run the binary
61 | * Now you can connect the MinDAV by using your favorite WebDAV clients
62 |
63 | ## Quick Run Minio Server
64 | ```sh
65 | docker run --name minio --rm -it \
66 | -p "9000:9000" \
67 | -v "./minio/data:/data" \
68 | -v "./minio/config:/root/.minio" \
69 | minio/minio:latest \
70 | server /data
71 | ```
72 |
73 | ## Supported Clients(KNOWN):
74 | * [Cyberduck](http://cyberduck.io) for `macOS`, `Windows`
75 | * [PhotoSync](http://www.photosync-app.com) for `iOS`, `Android`
76 | * [FE File Explorer](http://www.skyjos.com) for `iOS`, `Android`, `macOS`
77 | * [Filezilla](https://filezilla-project.org/) for `macOS`, `Windows`, `Linux`
78 | * [rclone](https://rclone.org/webdav/) for `macOS`, `Windows`, `Linux`
79 | * And More...
80 | > `OSX`'s `finder` is not support for `rename` operate!
81 |
82 | ## MEMORY_UPLOAD_MODE
83 | > If the host has a large memory, then set to `true` could improve upload performance.
84 |
85 | ## Roadmap
86 | - [x] Memory filesystem support
87 | - [x] File filesystem support
88 | - [x] Minio filesystem support
89 | - [x] User system
90 |
91 | ## Thanks
92 | * [Totoval](https://github.com/totoval/totoval)
93 |
--------------------------------------------------------------------------------
/app/cache/pbs.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: pbs.proto
3 |
4 | package pbs
5 |
6 | import (
7 | fmt "fmt"
8 | proto "github.com/golang/protobuf/proto"
9 | math "math"
10 | )
11 |
12 | // Reference imports to suppress errors if they are not otherwise used.
13 | var _ = proto.Marshal
14 | var _ = fmt.Errorf
15 | var _ = math.Inf
16 |
17 | // This is a compile-time assertion to ensure that this generated file
18 | // is compatible with the proto package it is being compiled against.
19 | // A compilation error at this line likely means your copy of the
20 | // proto package needs to be updated.
21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
22 |
23 | type Test struct {
24 | UserId uint32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
25 | UserName string `protobuf:"bytes,2,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"`
26 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
27 | XXX_unrecognized []byte `json:"-"`
28 | XXX_sizecache int32 `json:"-"`
29 | }
30 |
31 | func (m *Test) Reset() { *m = Test{} }
32 | func (m *Test) String() string { return proto.CompactTextString(m) }
33 | func (*Test) ProtoMessage() {}
34 | func (*Test) Descriptor() ([]byte, []int) {
35 | return fileDescriptor_f262ac8d8cf0a40b, []int{0}
36 | }
37 |
38 | func (m *Test) XXX_Unmarshal(b []byte) error {
39 | return xxx_messageInfo_Test.Unmarshal(m, b)
40 | }
41 | func (m *Test) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
42 | return xxx_messageInfo_Test.Marshal(b, m, deterministic)
43 | }
44 | func (m *Test) XXX_Merge(src proto.Message) {
45 | xxx_messageInfo_Test.Merge(m, src)
46 | }
47 | func (m *Test) XXX_Size() int {
48 | return xxx_messageInfo_Test.Size(m)
49 | }
50 | func (m *Test) XXX_DiscardUnknown() {
51 | xxx_messageInfo_Test.DiscardUnknown(m)
52 | }
53 |
54 | var xxx_messageInfo_Test proto.InternalMessageInfo
55 |
56 | func (m *Test) GetUserId() uint32 {
57 | if m != nil {
58 | return m.UserId
59 | }
60 | return 0
61 | }
62 |
63 | func (m *Test) GetUserName() string {
64 | if m != nil {
65 | return m.UserName
66 | }
67 | return ""
68 | }
69 |
70 | func init() {
71 | proto.RegisterType((*Test)(nil), "Test")
72 | }
73 |
74 | func init() { proto.RegisterFile("pbs.proto", fileDescriptor_f262ac8d8cf0a40b) }
75 |
76 | var fileDescriptor_f262ac8d8cf0a40b = []byte{
77 | // 95 bytes of a gzipped FileDescriptorProto
78 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2c, 0x48, 0x2a, 0xd6,
79 | 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0xb2, 0xe1, 0x62, 0x09, 0x49, 0x2d, 0x2e, 0x11, 0x12, 0xe7,
80 | 0x62, 0x2f, 0x2d, 0x4e, 0x2d, 0x8a, 0xcf, 0x4c, 0x91, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0d, 0x62,
81 | 0x03, 0x71, 0x3d, 0x53, 0x84, 0xa4, 0xb9, 0x38, 0xc1, 0x12, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x4c,
82 | 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x1c, 0x20, 0x01, 0xbf, 0xc4, 0xdc, 0xd4, 0x24, 0x36, 0xb0, 0x21,
83 | 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x25, 0xfc, 0x02, 0x51, 0x00, 0x00, 0x00,
84 | }
85 |
--------------------------------------------------------------------------------
/app/cache/pbs.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | message Test {
4 | uint32 user_id = 1;
5 | string user_name = 2;
6 | }
7 |
--------------------------------------------------------------------------------
/app/console/commands/hello.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/totoval/framework/cmd"
7 | )
8 |
9 | func init() {
10 | cmd.Add(&HelloWorld{})
11 | }
12 |
13 | type HelloWorld struct {
14 | }
15 |
16 | func (hw *HelloWorld) Command() string {
17 | return "say:hello-world {hi}"
18 | }
19 |
20 | func (hw *HelloWorld) Description() string {
21 | return "Say Hello"
22 | }
23 |
24 | func (hw *HelloWorld) Handler(arg *cmd.Arg) error {
25 | hi, err := arg.Get("hi")
26 | if err != nil {
27 | return err
28 | }
29 |
30 | if hi == nil {
31 | fmt.Println("Hello World")
32 | return nil
33 | }
34 |
35 | fmt.Println(*hi)
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/app/console/commands/kernel.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | func Initialize() {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/app/console/schedule.go:
--------------------------------------------------------------------------------
1 | package console
2 |
3 | import "github.com/totoval/framework/cmd"
4 |
5 | func Schedule(schedule *cmd.Schedule) {
6 | schedule.Command("say:hello-world hi,totoval").EverySecond()
7 | }
8 |
--------------------------------------------------------------------------------
/app/events/kernel.go:
--------------------------------------------------------------------------------
1 | package events
2 |
3 | func Initialize() {
4 | }
5 |
--------------------------------------------------------------------------------
/app/events/protocol_buffers/pbs.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: pbs.proto
3 |
4 | package pbs
5 |
6 | import (
7 | fmt "fmt"
8 | proto "github.com/golang/protobuf/proto"
9 | math "math"
10 | )
11 |
12 | // Reference imports to suppress errors if they are not otherwise used.
13 | var _ = proto.Marshal
14 | var _ = fmt.Errorf
15 | var _ = math.Inf
16 |
17 | // This is a compile-time assertion to ensure that this generated file
18 | // is compatible with the proto package it is being compiled against.
19 | // A compilation error at this line likely means your copy of the
20 | // proto package needs to be updated.
21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
22 |
23 | type UserRegistered struct {
24 | UserId uint32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
25 | AffiliationFromCode string `protobuf:"bytes,2,opt,name=affiliation_from_code,json=affiliationFromCode,proto3" json:"affiliation_from_code,omitempty"`
26 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
27 | XXX_unrecognized []byte `json:"-"`
28 | XXX_sizecache int32 `json:"-"`
29 | }
30 |
31 | func (m *UserRegistered) Reset() { *m = UserRegistered{} }
32 | func (m *UserRegistered) String() string { return proto.CompactTextString(m) }
33 | func (*UserRegistered) ProtoMessage() {}
34 | func (*UserRegistered) Descriptor() ([]byte, []int) {
35 | return fileDescriptor_f262ac8d8cf0a40b, []int{0}
36 | }
37 |
38 | func (m *UserRegistered) XXX_Unmarshal(b []byte) error {
39 | return xxx_messageInfo_UserRegistered.Unmarshal(m, b)
40 | }
41 | func (m *UserRegistered) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
42 | return xxx_messageInfo_UserRegistered.Marshal(b, m, deterministic)
43 | }
44 | func (m *UserRegistered) XXX_Merge(src proto.Message) {
45 | xxx_messageInfo_UserRegistered.Merge(m, src)
46 | }
47 | func (m *UserRegistered) XXX_Size() int {
48 | return xxx_messageInfo_UserRegistered.Size(m)
49 | }
50 | func (m *UserRegistered) XXX_DiscardUnknown() {
51 | xxx_messageInfo_UserRegistered.DiscardUnknown(m)
52 | }
53 |
54 | var xxx_messageInfo_UserRegistered proto.InternalMessageInfo
55 |
56 | func (m *UserRegistered) GetUserId() uint32 {
57 | if m != nil {
58 | return m.UserId
59 | }
60 | return 0
61 | }
62 |
63 | func (m *UserRegistered) GetAffiliationFromCode() string {
64 | if m != nil {
65 | return m.AffiliationFromCode
66 | }
67 | return ""
68 | }
69 |
70 | func init() {
71 | proto.RegisterType((*UserRegistered)(nil), "UserRegistered")
72 | }
73 |
74 | func init() { proto.RegisterFile("pbs.proto", fileDescriptor_f262ac8d8cf0a40b) }
75 |
76 | var fileDescriptor_f262ac8d8cf0a40b = []byte{
77 | // 124 bytes of a gzipped FileDescriptorProto
78 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2c, 0x48, 0x2a, 0xd6,
79 | 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x8a, 0xe5, 0xe2, 0x0b, 0x2d, 0x4e, 0x2d, 0x0a, 0x4a, 0x4d,
80 | 0xcf, 0x2c, 0x2e, 0x49, 0x2d, 0x4a, 0x4d, 0x11, 0x12, 0xe7, 0x62, 0x2f, 0x2d, 0x4e, 0x2d, 0x8a,
81 | 0xcf, 0x4c, 0x91, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0d, 0x62, 0x03, 0x71, 0x3d, 0x53, 0x84, 0x8c,
82 | 0xb8, 0x44, 0x13, 0xd3, 0xd2, 0x32, 0x73, 0x32, 0x13, 0x4b, 0x32, 0xf3, 0xf3, 0xe2, 0xd3, 0x8a,
83 | 0xf2, 0x73, 0xe3, 0x93, 0xf3, 0x53, 0x52, 0x25, 0x98, 0x14, 0x18, 0x35, 0x38, 0x83, 0x84, 0x91,
84 | 0x24, 0xdd, 0x8a, 0xf2, 0x73, 0x9d, 0xf3, 0x53, 0x52, 0x93, 0xd8, 0xc0, 0xb6, 0x18, 0x03, 0x02,
85 | 0x00, 0x00, 0xff, 0xff, 0x6e, 0x83, 0x59, 0x54, 0x72, 0x00, 0x00, 0x00,
86 | }
87 |
--------------------------------------------------------------------------------
/app/events/protocol_buffers/pbs.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | message UserRegistered {
4 | uint32 user_id = 1;
5 | string affiliation_from_code = 2;
6 | }
7 |
--------------------------------------------------------------------------------
/app/events/user_registered.go:
--------------------------------------------------------------------------------
1 | package events
2 |
3 | import (
4 | "github.com/golang/protobuf/proto"
5 |
6 | "github.com/totoval/framework/hub"
7 | pbs "totoval/app/events/protocol_buffers"
8 | )
9 |
10 | func init() {
11 | hub.Make(&UserRegistered{})
12 | }
13 |
14 | type UserRegistered struct {
15 | hub.Event
16 | }
17 |
18 | func (ur *UserRegistered) ParamProto() proto.Message {
19 | return &pbs.UserRegistered{}
20 | }
21 |
--------------------------------------------------------------------------------
/app/http/controllers/login.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/totoval/framework/config"
7 | "github.com/totoval/framework/helpers"
8 | "github.com/totoval/framework/helpers/m"
9 | "github.com/totoval/framework/helpers/toto"
10 | "github.com/totoval/framework/http/controller"
11 | "github.com/totoval/framework/request"
12 | "github.com/totoval/framework/utils/crypt"
13 | "github.com/totoval/framework/utils/jwt"
14 |
15 | "totoval/app/http/requests"
16 | "totoval/app/models"
17 | )
18 |
19 | type Login struct {
20 | controller.BaseController
21 | }
22 |
23 | func (l *Login) Login(c *request.Context) {
24 | // validate and assign requestData
25 | var requestData requests.UserLogin
26 | if !l.Validate(c, &requestData, true) {
27 | return
28 | }
29 |
30 | user := models.User{
31 | Email: &requestData.Email,
32 | }
33 | if err := m.H().First(&user, false); err != nil {
34 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": helpers.L(c, "auth.login.failed_not_exist")})
35 | return
36 | }
37 |
38 | if !crypt.BcryptCheck(*user.Password, requestData.Password) {
39 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": helpers.L(c, "auth.login.failed_wrong_password")})
40 | return
41 | }
42 |
43 | // create jwt
44 | newJwt := jwt.NewJWT(config.GetString("auth.sign_key"))
45 | username := ""
46 | if user.Name != nil {
47 | username = *user.Name
48 | }
49 | if token, err := newJwt.CreateToken(string(*user.ID), username); err == nil {
50 | c.JSON(http.StatusOK, toto.V{"token": token})
51 | return
52 | }
53 |
54 | c.JSON(http.StatusOK, toto.V{"error": helpers.L(c, "auth.login.failed_token_generate_error")})
55 | return
56 | }
57 |
--------------------------------------------------------------------------------
/app/http/controllers/register.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 |
7 | "github.com/totoval/framework/helpers/log"
8 | "github.com/totoval/framework/helpers/toto"
9 | "github.com/totoval/framework/request"
10 |
11 | "github.com/totoval/framework/hub"
12 |
13 | "github.com/totoval/framework/config"
14 | "github.com/totoval/framework/helpers"
15 | "github.com/totoval/framework/helpers/m"
16 | "github.com/totoval/framework/http/controller"
17 | "github.com/totoval/framework/model/helper"
18 | "github.com/totoval/framework/utils/crypt"
19 | "github.com/totoval/framework/utils/jwt"
20 |
21 | "totoval/app/events"
22 | pbs "totoval/app/events/protocol_buffers"
23 | "totoval/app/http/requests"
24 | "totoval/app/models"
25 | )
26 |
27 | type Register struct {
28 | controller.BaseController
29 | }
30 |
31 | func (r *Register) Register(c *request.Context) {
32 | // validate and assign requestData
33 | var requestData requests.UserRegister
34 | if !r.Validate(c, &requestData, true) {
35 | return
36 | }
37 |
38 | defer func() {
39 | if err := recover(); err != nil {
40 | responseErr, ok := err.(error)
41 | if ok {
42 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": responseErr.Error()})
43 | return
44 | }
45 | panic(err)
46 | }
47 | }()
48 |
49 | var token string
50 | var userId uint
51 | m.Transaction(func(TransactionHelper *helper.Helper) {
52 | // determine if exist
53 | user := models.User{
54 | Email: &requestData.Email,
55 | }
56 | if TransactionHelper.Exist(&user, true) {
57 | panic(errors.New(helpers.L(c, "auth.register.failed_existed")))
58 | }
59 |
60 | // create user
61 | // encrypt password //@todo move to model setter later
62 | encryptedPassword := crypt.Bcrypt(requestData.Password)
63 | user.Password = &encryptedPassword
64 | if err := TransactionHelper.Create(&user); err != nil {
65 | panic(errors.New(helpers.L(c, "auth.register.failed_system_error")))
66 | }
67 |
68 | // create jwt
69 | newJwt := jwt.NewJWT(config.GetString("auth.sign_key"))
70 | username := ""
71 | if user.Name != nil {
72 | username = *user.Name
73 | }
74 | var err error
75 | token, err = newJwt.CreateToken(string(*user.ID), username)
76 | if err != nil {
77 | panic(helpers.L(c, "auth.register.failed_token_generate_error"))
78 | }
79 |
80 | userId = *user.ID
81 | }, 1)
82 |
83 | // emit user-registered event
84 | ur := events.UserRegistered{}
85 | param := &pbs.UserRegistered{
86 | UserId: uint32(userId),
87 | AffiliationFromCode: "",
88 | }
89 | if requestData.AffiliationFromCode != nil {
90 | param.AffiliationFromCode = *requestData.AffiliationFromCode
91 | }
92 | ur.SetParam(param)
93 | if errs := hub.Emit(&ur); errs != nil {
94 | log.Info("user registered event emit failed", toto.V{"event": ur, "errors": errs})
95 | }
96 |
97 | c.JSON(http.StatusOK, toto.V{"token": token})
98 | return
99 | }
100 |
--------------------------------------------------------------------------------
/app/http/controllers/user.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/totoval/framework/helpers/m"
7 | "github.com/totoval/framework/helpers/ptr"
8 | "github.com/totoval/framework/helpers/toto"
9 | "github.com/totoval/framework/http/controller"
10 | "github.com/totoval/framework/http/middleware"
11 | "github.com/totoval/framework/model"
12 | "github.com/totoval/framework/policy"
13 | "github.com/totoval/framework/request"
14 |
15 | "totoval/app/models"
16 | "totoval/app/policies"
17 | )
18 |
19 | type User struct {
20 | controller.BaseController
21 | }
22 |
23 | func (*User) LogOut(c *request.Context) {
24 | if err := middleware.Revoke(c); err != nil {
25 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": err.Error()})
26 | return
27 | }
28 | c.JSON(http.StatusOK, toto.V{})
29 | return
30 | }
31 |
32 | func (u *User) Info(c *request.Context) {
33 | if u.Scan(c) {
34 | return
35 | }
36 | user := u.User().Value().(*models.User)
37 |
38 | if permit, _ := u.Authorize(c, policies.NewUserPolicy(), policy.ActionView); !permit {
39 | c.JSON(http.StatusForbidden, toto.V{"error": policy.UserNotPermitError{}.Error()})
40 | return
41 | }
42 |
43 | user.Password = ptr.String("") // remove password value for response rendering
44 | c.JSON(http.StatusOK, toto.V{"data": user})
45 | return
46 | }
47 |
48 | func (*User) AllUser(c *request.Context) {
49 | user := &models.User{}
50 | outArr, err := user.ObjArr([]model.Filter{}, []model.Sort{}, 0, false)
51 | if err != nil {
52 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": err.Error()})
53 | return
54 | }
55 | c.JSON(http.StatusOK, toto.V{"data": outArr.([]models.User)})
56 | return
57 | }
58 |
59 | func (*User) PaginateUser(c *request.Context) {
60 | user := &models.User{}
61 | pagination, err := user.ObjArrPaginate(c, 25, []model.Filter{}, []model.Sort{}, 0, false)
62 | if err != nil {
63 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": err.Error()})
64 | return
65 | }
66 | c.JSON(http.StatusOK, toto.V{"data": toto.V{"item": pagination.ItemArr(), "totalPage": pagination.LastPage(), "currentPage": pagination.CurrentPage(), "count": pagination.Count(), "total": pagination.Total()}})
67 | return
68 | }
69 |
70 | func (*User) Update(c *request.Context) {
71 | var id uint
72 | id = 14
73 | user := models.User{
74 | ID: &id,
75 | }
76 | if err := m.H().First(&user, false); err != nil {
77 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": err.Error()})
78 | return
79 | }
80 |
81 | name := "t2222es123t"
82 | modifyUser := models.User{
83 | Name: &name,
84 | }
85 | if err := m.H().Save(&user, modifyUser); err != nil {
86 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": err})
87 | return
88 | }
89 | c.JSON(http.StatusOK, toto.V{"data": user})
90 | return
91 |
92 | // m.Transaction(func() {
93 | // fmt.Println(id)
94 | // panic(123)
95 | // }, 3)
96 | }
97 | func (*User) Delete(c *request.Context) {
98 | var id uint
99 | id = 14
100 | user := models.User{
101 | ID: &id,
102 | }
103 | if err := m.H().Delete(&user, false); err != nil {
104 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": err})
105 | return
106 | }
107 | c.JSON(http.StatusOK, toto.V{"data": true})
108 | return
109 | }
110 | func (*User) DeleteTransaction(c *request.Context) {
111 | defer func() { // handle transaction error
112 | if err := recover(); err != nil {
113 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": err.(error).Error()})
114 | return
115 | }
116 | }()
117 |
118 | var id uint
119 | id = 14
120 | user := models.User{
121 | ID: &id,
122 | }
123 | m.Transaction(func(h *m.Helper) {
124 | user.SetTX(h.DB()) // important
125 | if err := h.Delete(&user, false); err != nil {
126 | panic(err)
127 | }
128 | }, 1)
129 |
130 | c.JSON(http.StatusOK, toto.V{"data": true})
131 | return
132 | }
133 | func (*User) Restore(c *request.Context) {
134 | var id uint
135 | id = 14
136 | modifyUser := models.User{
137 | ID: &id,
138 | }
139 |
140 | if err := m.H().Restore(&modifyUser); err != nil {
141 | c.JSON(http.StatusUnprocessableEntity, toto.V{"error": err})
142 | return
143 | }
144 | c.JSON(http.StatusOK, toto.V{"data": true})
145 | return
146 | }
147 |
--------------------------------------------------------------------------------
/app/http/controllers/user_affiliation.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/totoval/framework/helpers/toto"
7 | "github.com/totoval/framework/http/controller"
8 | "github.com/totoval/framework/request"
9 | "totoval/app/models"
10 | )
11 |
12 | type UserAffiliation struct {
13 | controller.BaseController
14 | }
15 |
16 | func (uaff *UserAffiliation) RenderAll(c *request.Context) {
17 | var u models.UserAffiliation
18 | c.HTML(http.StatusOK, "user_affiliation.nodes", toto.V{
19 | "data": u.All(),
20 | })
21 |
22 | return
23 | }
24 |
--------------------------------------------------------------------------------
/app/http/controllers/webdav.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/totoval/framework/http/controller"
5 | "github.com/totoval/framework/request"
6 | "totoval/app/logics/mindav"
7 | )
8 |
9 | type WebDAV struct {
10 | controller.BaseController
11 | }
12 |
13 | func (wd *WebDAV) Handle(c *request.Context) {
14 | mindav.Handler().ServeHTTP(c.Writer, c.Request)
15 | return
16 | }
17 |
--------------------------------------------------------------------------------
/app/http/middleware/example.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/totoval/framework/helpers/log"
5 | "github.com/totoval/framework/helpers/zone"
6 | )
7 |
8 | func Example() request.HandlerFunc {
9 | return func(c *request.Context) {
10 | t := zone.Now()
11 |
12 | // Set example variable
13 | c.Set("example", "12345")
14 |
15 | // before request
16 |
17 | c.Next()
18 |
19 | // after request
20 | latency := zone.Since(t)
21 | log.Info("latency", toto.V{"latency": latency})
22 |
23 | // access the status we are sending
24 | status := c.Writer.Status()
25 | log.Info("status", toto.V{"status": status})
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/http/requests/login.go:
--------------------------------------------------------------------------------
1 | package requests
2 |
3 | type UserLogin struct {
4 | Email string `json:"email" binding:"required,email"`
5 | Password string `json:"password" binding:"required,min=8,max=24"`
6 | }
7 |
--------------------------------------------------------------------------------
/app/http/requests/register.go:
--------------------------------------------------------------------------------
1 | package requests
2 |
3 | type UserRegister struct {
4 | Email string `json:"email" binding:"required,email"`
5 | AffiliationFromCode *string `json:"affiliation_code" binding:"omitempty,len=6"`
6 | Password string `json:"password" binding:"required,min=8,max=24"`
7 | PasswordConfirmation string `json:"password_confirmation" binding:"required,eqfield=Password"`
8 | }
9 |
--------------------------------------------------------------------------------
/app/jobs/example.go:
--------------------------------------------------------------------------------
1 | package jobs
2 |
3 | import (
4 | "github.com/golang/protobuf/proto"
5 |
6 | "github.com/totoval/framework/helpers/debug"
7 | "github.com/totoval/framework/job"
8 | pbs "totoval/app/jobs/protocol_buffers"
9 | )
10 |
11 | func init() {
12 | job.Add(&ExampleJob{})
13 | }
14 |
15 | type ExampleJob struct {
16 | job.Job
17 | }
18 |
19 | func (e *ExampleJob) Retries() uint32 {
20 | return 3
21 | }
22 |
23 | func (e *ExampleJob) Name() string {
24 | return "example-job"
25 | }
26 |
27 | func (e *ExampleJob) ParamProto() proto.Message {
28 | return &pbs.ExampleJob{}
29 | }
30 |
31 | func (e *ExampleJob) Handle(paramPtr proto.Message) error {
32 | obj := paramPtr.(*pbs.ExampleJob)
33 | debug.Dump(obj)
34 | return nil
35 | }
36 |
--------------------------------------------------------------------------------
/app/jobs/kernel.go:
--------------------------------------------------------------------------------
1 | package jobs
2 |
3 | import "github.com/totoval/framework/job"
4 |
5 | func Initialize() {
6 | // initialize topic and channel
7 | job.RegisterQueue()
8 | }
9 |
--------------------------------------------------------------------------------
/app/jobs/protocol_buffers/pbs.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: pbs.proto
3 |
4 | package pbs
5 |
6 | import (
7 | fmt "fmt"
8 | proto "github.com/golang/protobuf/proto"
9 | math "math"
10 | )
11 |
12 | // Reference imports to suppress errors if they are not otherwise used.
13 | var _ = proto.Marshal
14 | var _ = fmt.Errorf
15 | var _ = math.Inf
16 |
17 | // This is a compile-time assertion to ensure that this generated file
18 | // is compatible with the proto package it is being compiled against.
19 | // A compilation error at this line likely means your copy of the
20 | // proto package needs to be updated.
21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
22 |
23 | type ExampleJob struct {
24 | Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`
25 | PageNumber int32 `protobuf:"varint,2,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"`
26 | ResultPerPage int32 `protobuf:"varint,3,opt,name=result_per_page,json=resultPerPage,proto3" json:"result_per_page,omitempty"`
27 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
28 | XXX_unrecognized []byte `json:"-"`
29 | XXX_sizecache int32 `json:"-"`
30 | }
31 |
32 | func (m *ExampleJob) Reset() { *m = ExampleJob{} }
33 | func (m *ExampleJob) String() string { return proto.CompactTextString(m) }
34 | func (*ExampleJob) ProtoMessage() {}
35 | func (*ExampleJob) Descriptor() ([]byte, []int) {
36 | return fileDescriptor_f262ac8d8cf0a40b, []int{0}
37 | }
38 |
39 | func (m *ExampleJob) XXX_Unmarshal(b []byte) error {
40 | return xxx_messageInfo_ExampleJob.Unmarshal(m, b)
41 | }
42 | func (m *ExampleJob) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
43 | return xxx_messageInfo_ExampleJob.Marshal(b, m, deterministic)
44 | }
45 | func (m *ExampleJob) XXX_Merge(src proto.Message) {
46 | xxx_messageInfo_ExampleJob.Merge(m, src)
47 | }
48 | func (m *ExampleJob) XXX_Size() int {
49 | return xxx_messageInfo_ExampleJob.Size(m)
50 | }
51 | func (m *ExampleJob) XXX_DiscardUnknown() {
52 | xxx_messageInfo_ExampleJob.DiscardUnknown(m)
53 | }
54 |
55 | var xxx_messageInfo_ExampleJob proto.InternalMessageInfo
56 |
57 | func (m *ExampleJob) GetQuery() string {
58 | if m != nil {
59 | return m.Query
60 | }
61 | return ""
62 | }
63 |
64 | func (m *ExampleJob) GetPageNumber() int32 {
65 | if m != nil {
66 | return m.PageNumber
67 | }
68 | return 0
69 | }
70 |
71 | func (m *ExampleJob) GetResultPerPage() int32 {
72 | if m != nil {
73 | return m.ResultPerPage
74 | }
75 | return 0
76 | }
77 |
78 | func init() {
79 | proto.RegisterType((*ExampleJob)(nil), "exampleJob")
80 | }
81 |
82 | func init() { proto.RegisterFile("pbs.proto", fileDescriptor_f262ac8d8cf0a40b) }
83 |
84 | var fileDescriptor_f262ac8d8cf0a40b = []byte{
85 | // 130 bytes of a gzipped FileDescriptorProto
86 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2c, 0x48, 0x2a, 0xd6,
87 | 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0xca, 0xe6, 0xe2, 0x4a, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49,
88 | 0xf5, 0xca, 0x4f, 0x12, 0x12, 0xe1, 0x62, 0x2d, 0x2c, 0x4d, 0x2d, 0xaa, 0x94, 0x60, 0x54, 0x60,
89 | 0xd4, 0xe0, 0x0c, 0x82, 0x70, 0x84, 0xe4, 0xb9, 0xb8, 0x0b, 0x12, 0xd3, 0x53, 0xe3, 0xf3, 0x4a,
90 | 0x73, 0x93, 0x52, 0x8b, 0x24, 0x98, 0x14, 0x18, 0x35, 0x58, 0x83, 0xb8, 0x40, 0x42, 0x7e, 0x60,
91 | 0x11, 0x21, 0x35, 0x2e, 0xfe, 0xa2, 0xd4, 0xe2, 0xd2, 0x9c, 0x92, 0xf8, 0x82, 0xd4, 0xa2, 0x78,
92 | 0x90, 0x84, 0x04, 0x33, 0x58, 0x11, 0x2f, 0x44, 0x38, 0x20, 0xb5, 0x28, 0x20, 0x31, 0x3d, 0x35,
93 | 0x89, 0x0d, 0x6c, 0xa7, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xe8, 0x41, 0x4e, 0x0c, 0x80, 0x00,
94 | 0x00, 0x00,
95 | }
96 |
--------------------------------------------------------------------------------
/app/jobs/protocol_buffers/pbs.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | message exampleJob {
4 | string query = 1;
5 | int32 page_number = 2;
6 | int32 result_per_page = 3;
7 | }
8 |
--------------------------------------------------------------------------------
/app/listeners/add_user_affiliation.go:
--------------------------------------------------------------------------------
1 | package listeners
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/golang/protobuf/proto"
7 |
8 | "github.com/totoval/framework/config"
9 | "github.com/totoval/framework/helpers/m"
10 | "github.com/totoval/framework/hub"
11 |
12 | "totoval/app/events"
13 | pbs "totoval/app/events/protocol_buffers"
14 | "totoval/app/models"
15 | )
16 |
17 | func init() {
18 | hub.Register(&AddUserAffiliation{})
19 | }
20 |
21 | type AddUserAffiliation struct {
22 | user models.User
23 | affiliationFromCode *string
24 | hub.Listen
25 | }
26 |
27 | func (auaff *AddUserAffiliation) Name() hub.ListenerName {
28 | return "add-user-affiliation"
29 | }
30 |
31 | func (auaff *AddUserAffiliation) Subscribe() (eventPtrList []hub.Eventer) {
32 | return []hub.Eventer{
33 | &events.UserRegistered{},
34 | }
35 | }
36 |
37 | func (auaff *AddUserAffiliation) Construct(paramPtr proto.Message) error {
38 | // event type assertion
39 | param, ok := paramPtr.(*pbs.UserRegistered)
40 | if !ok {
41 | return errors.New("listener param is invalid")
42 | }
43 |
44 | auaff.affiliationFromCode = nil
45 | if param.AffiliationFromCode != "" && checkFromCode(param.AffiliationFromCode) {
46 | auaff.affiliationFromCode = ¶m.AffiliationFromCode
47 | }
48 |
49 | uid := uint(param.GetUserId())
50 | auaff.user = models.User{ID: &uid}
51 | if err := m.H().First(&auaff.user, false); err != nil {
52 | return err
53 | }
54 |
55 | return nil
56 | }
57 |
58 | func (auaff *AddUserAffiliation) Handle() error {
59 | // add user affiliation
60 | if config.GetBool("user_affiliation.enable") {
61 | uaffPtr := &models.UserAffiliation{
62 | UserID: auaff.user.ID,
63 | }
64 | var err error
65 | if auaff.affiliationFromCode != nil {
66 | err = uaffPtr.InsertNode(&auaff.user, *auaff.affiliationFromCode)
67 | } else {
68 | err = uaffPtr.InsertNode(&auaff.user)
69 | }
70 | if err != nil {
71 | return errors.New("user affiliation insert failed")
72 | }
73 | }
74 |
75 | return nil
76 | }
77 |
78 | // check affiliationFromCode is valid
79 | func checkFromCode(affiliationFromCode string) bool {
80 | uaff := models.UserAffiliation{
81 | Code: &affiliationFromCode,
82 | }
83 | return m.H().Exist(&uaff, false)
84 | }
85 |
--------------------------------------------------------------------------------
/app/listeners/kernel.go:
--------------------------------------------------------------------------------
1 | package listeners
2 |
3 | import "github.com/totoval/framework/hub"
4 |
5 | func Initialize() {
6 | // initialize topic and channel
7 | hub.RegisterQueue()
8 | }
9 |
--------------------------------------------------------------------------------
/app/logics/mindav/driver/miniofs/fileinfo.go:
--------------------------------------------------------------------------------
1 | package miniofs
2 |
3 | import (
4 | "github.com/minio/minio-go/v6"
5 | "github.com/totoval/framework/helpers/log"
6 | "github.com/totoval/framework/helpers/toto"
7 | "github.com/totoval/framework/helpers/zone"
8 | "os"
9 | "path"
10 | "strings"
11 | )
12 |
13 | type fileInfo struct {
14 | minio.ObjectInfo
15 | }
16 |
17 | func (moi *fileInfo) Name() string {
18 | name := moi.ObjectInfo.Key
19 |
20 | name = strings.Trim(name, "/")
21 |
22 | if strings.Contains(name, "/") {
23 | name = path.Clean(strings.Replace(name, path.Dir(name), "", 1))
24 | }
25 | log.Debug(name, toto.V{"Key": moi.ObjectInfo.Key, "ObjectName": name})
26 |
27 | return name
28 | } // base name of the file
29 | func (moi *fileInfo) Size() int64 {
30 | return moi.ObjectInfo.Size
31 | } // length in bytes for regular files; system-dependent for others
32 | func (moi *fileInfo) Mode() os.FileMode {
33 | return 777
34 | } // file mode bits
35 | func (moi *fileInfo) ModTime() zone.Time {
36 | return moi.ObjectInfo.LastModified
37 | } // modification time
38 | func (moi *fileInfo) IsDir() bool {
39 | isDir := moi.ObjectInfo.ContentType == "inode/directory"
40 | log.Debug(moi.ObjectInfo.Key, toto.V{"IsDir": isDir})
41 | return isDir
42 | } // abbreviation for Mode().IsDir()
43 | func (moi *fileInfo) Sys() interface{} {
44 | return nil
45 | } // underlying data source (can return nil)
46 |
--------------------------------------------------------------------------------
/app/logics/mindav/driver/miniofs/interfaces.go:
--------------------------------------------------------------------------------
1 | package miniofs
2 |
3 | import (
4 | "context"
5 | "github.com/minio/minio-go/v6"
6 | "github.com/totoval/framework/helpers/zone"
7 | "io"
8 | "net/url"
9 | )
10 |
11 | type MinioClienter interface {
12 | MinioLser
13 | MinioUploader
14 | MinioStater
15 | MinioGetter
16 | }
17 |
18 | type MinioLser interface {
19 | ListObjects(bucketName, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan minio.ObjectInfo
20 | ListObjectsV2(bucketName, objectPrefix string, recursive bool, doneCh <-chan struct{}) <-chan minio.ObjectInfo
21 | }
22 |
23 | type MinioUploader interface {
24 | PutObject(bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (n int64, err error)
25 | PutObjectWithContext(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (n int64, err error)
26 | PresignedPutObject(bucketName string, objectName string, expires zone.Duration) (u *url.URL, err error)
27 | FPutObject(bucketName, objectName, filePath string, opts minio.PutObjectOptions) (n int64, err error)
28 | FPutObjectWithContext(ctx context.Context, bucketName, objectName, filePath string, opts minio.PutObjectOptions) (n int64, err error)
29 | }
30 |
31 | type MinioStater interface {
32 | StatObject(bucketName, objectName string, opts minio.StatObjectOptions) (minio.ObjectInfo, error)
33 | }
34 | type MinioGetter interface {
35 | GetObject(bucketName, objectName string, opts minio.GetObjectOptions) (*minio.Object, error)
36 | GetObjectACL(bucketName, objectName string) (*minio.ObjectInfo, error)
37 | GetObjectWithContext(ctx context.Context, bucketName, objectName string, opts minio.GetObjectOptions) (*minio.Object, error)
38 | PresignedGetObject(bucketName string, objectName string, expires zone.Duration, reqParams url.Values) (u *url.URL, err error)
39 | FGetObject(bucketName, objectName, filePath string, opts minio.GetObjectOptions) error
40 | FGetObjectWithContext(ctx context.Context, bucketName, objectName, filePath string, opts minio.GetObjectOptions) error
41 | }
42 |
--------------------------------------------------------------------------------
/app/logics/mindav/driver/miniofs/minio.go:
--------------------------------------------------------------------------------
1 | package miniofs
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "fmt"
7 | "github.com/minio/minio-go/v6"
8 | "github.com/totoval/framework/config"
9 | "github.com/totoval/framework/helpers/cache"
10 | "github.com/totoval/framework/helpers/log"
11 | "github.com/totoval/framework/helpers/toto"
12 | "github.com/totoval/framework/helpers/zone"
13 | "golang.org/x/net/webdav"
14 | "os"
15 | "path"
16 | "strings"
17 | )
18 |
19 | const KEEP_FILE_NAME = ".mindavkeep"
20 | const KEEP_FILE_CONTENT_TYPE = "application/mindav-folder-keeper"
21 |
22 | type minioFileSystem struct {
23 | Endpoint string
24 | AccessKeyID string
25 | SecretAccessKey string
26 | UseSSL bool
27 | client *minio.Client
28 | bucketName string
29 | location string
30 | rootInfo *fileInfo
31 | rootFile *file
32 | uploadTmpPath string
33 | }
34 |
35 | func New(bucketName string, location string) *minioFileSystem {
36 | m := &minioFileSystem{
37 | Endpoint: config.GetString("webdav.filesystems.minio.endpoint"),
38 | AccessKeyID: config.GetString("webdav.filesystems.minio.access_key_id"),
39 | SecretAccessKey: config.GetString("webdav.filesystems.minio.secret_access_key"),
40 | UseSSL: config.GetBool("webdav.filesystems.minio.use_ssl"),
41 | bucketName: bucketName,
42 | location: location,
43 | uploadTmpPath: ".",
44 | rootInfo: &fileInfo{minio.ObjectInfo{
45 | Key: "/",
46 | Size: 0,
47 | LastModified: zone.Now(),
48 | ContentType: "inode/directory",
49 | ETag: "",
50 | StorageClass: "",
51 | }},
52 | }
53 | m.rootFile = &file{m, nil, "/"}
54 |
55 | var err error
56 | if m.client, err = minio.New(m.Endpoint, m.AccessKeyID, m.SecretAccessKey, m.UseSSL); err != nil {
57 | panic(err)
58 | }
59 |
60 | err = m.MkBucket()
61 | if err != nil {
62 | panic(err)
63 | }
64 |
65 | return m
66 | }
67 | func clearName(name string) (string, error) {
68 | if name == "/" {
69 | return "", nil
70 | }
71 | slashed := strings.HasSuffix(name, "/")
72 | name = path.Clean(name)
73 | if !strings.HasSuffix(name, "/") && slashed {
74 | name += "/"
75 | }
76 | if !strings.HasPrefix(name, "/") {
77 | return "", os.ErrInvalid
78 | }
79 | return name, nil
80 | }
81 | func (m *minioFileSystem) MkBucket() (err error) {
82 | exists, err := m.client.BucketExists(m.bucketName)
83 | if err != nil {
84 | return log.Error(err, toto.V{"op": "mkbucket check"})
85 | }
86 |
87 | if exists {
88 | log.Info("We already own", toto.V{"bucket": m.bucketName})
89 | return nil
90 | }
91 |
92 | // not exist
93 | if err := m.client.MakeBucket(m.bucketName, m.location); err != nil {
94 | return log.Error(err, toto.V{"op": "mkbucket make"})
95 | }
96 |
97 | log.Info("Successfully created", toto.V{"bucket": m.bucketName})
98 | return nil
99 | }
100 | func (m *minioFileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
101 |
102 | name, err := clearName(name)
103 | if err != nil {
104 | return err
105 | }
106 |
107 | fileBytes := bytes.NewBuffer([]byte{})
108 | _, err = m.client.PutObject(m.bucketName, strings.TrimPrefix(path.Join(name, KEEP_FILE_NAME), "/"), bytes.NewBuffer([]byte{}), int64(fileBytes.Len()), minio.PutObjectOptions{ContentType: KEEP_FILE_CONTENT_TYPE})
109 | if err != nil {
110 | return log.Error(err, toto.V{"op": "mkdir", "name": path.Join(name, KEEP_FILE_NAME)})
111 | }
112 | log.Info("mkdir success", toto.V{"name": name})
113 | return nil
114 | }
115 | func (m *minioFileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
116 |
117 | name, err := clearName(name)
118 | if err != nil {
119 | return nil, err
120 | }
121 |
122 | log.Trace("minio openfile", toto.V{"Name": name})
123 |
124 | if len(name) == 0 {
125 | return m.rootFile, nil
126 | }
127 |
128 | // file
129 | object, err := m.client.GetObjectWithContext(ctx, m.bucketName, strings.TrimPrefix(name, "/"), minio.GetObjectOptions{})
130 | log.Trace("open file", toto.V{"name": name})
131 | if err != nil {
132 | return nil, err
133 | }
134 |
135 | return &file{m, object, name}, nil
136 | }
137 | func (m *minioFileSystem) RemoveAll(ctx context.Context, name string) error {
138 |
139 | name, err := clearName(name)
140 | if err != nil {
141 | return err
142 | }
143 |
144 | log.Trace("minio removeall", toto.V{"Name": name})
145 |
146 | objectsCh := make(chan string)
147 | // Send object names that are needed to be removed to objectsCh
148 | go func() {
149 | defer close(objectsCh)
150 | // List all objects from a bucket-name with a matching prefix.
151 | for object := range m.client.ListObjects(m.bucketName, name, true, nil) {
152 | if object.Err != nil {
153 | _ = log.Error(object.Err, toto.V{"op": "removeAll", "name": name})
154 | }
155 | objectsCh <- object.Key
156 | }
157 | }()
158 |
159 | for rErr := range m.client.RemoveObjectsWithContext(ctx, m.bucketName, objectsCh) {
160 | fmt.Println("Error detected during deletion: ", rErr)
161 |
162 | if rErr.Err != nil {
163 | return rErr.Err
164 | }
165 |
166 | deleteCacheIsDir(rErr.ObjectName)
167 | }
168 |
169 | deleteCacheIsDir(name)
170 | return m.client.RemoveObject(m.bucketName, name)
171 | }
172 | func (m *minioFileSystem) Rename(ctx context.Context, oldName, newName string) error {
173 |
174 | oldParentName, err := clearName(oldName)
175 | if err != nil {
176 | return err
177 | }
178 | newParentName, err := clearName(newName)
179 | if err != nil {
180 | return err
181 | }
182 |
183 | log.Trace("minio rename", toto.V{"Old": oldName, "New": newName, "oldParentName": oldParentName, "newParentName": newParentName})
184 |
185 | //newName = strings.Replace(newName, path.Dir(oldName), "", 1)
186 | err = m.walkDir(ctx, oldParentName, newParentName, oldName)
187 | if err != nil {
188 | return err
189 | }
190 |
191 | // return nil // for test
192 | return m.RemoveAll(ctx, oldName)
193 | }
194 | func (m *minioFileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) {
195 |
196 | name, err := clearName(name)
197 | if err != nil {
198 | return nil, err
199 | }
200 |
201 | log.Trace("minio stat", toto.V{"Name": name})
202 | if len(name) == 0 {
203 | // root dir
204 | return m.rootInfo, nil
205 | }
206 |
207 | stat, err := m.client.StatObject(m.bucketName, name, minio.StatObjectOptions{})
208 | if err != nil {
209 | if _err, ok := err.(minio.ErrorResponse); ok {
210 | if _err.Code == "NoSuchKey" {
211 | // check is dir
212 | if !m.isDir(name) {
213 | // not exist
214 | return nil, os.ErrNotExist
215 | }
216 |
217 | // is dir
218 | theName, err := clearName(name)
219 | if err != nil {
220 | return nil, err
221 | }
222 | return &fileInfo{minio.ObjectInfo{
223 | Key: theName,
224 | Size: 0,
225 | LastModified: zone.Now(),
226 | ContentType: "inode/directory",
227 | ETag: "",
228 | StorageClass: "",
229 | }}, nil
230 | }
231 | }
232 | return nil, log.Error(err)
233 | }
234 | return &fileInfo{stat}, nil
235 | }
236 | func (m *minioFileSystem) walkDir(ctx context.Context, oldParentName, newParentName, oldName string) error {
237 |
238 | oldNameTrim := strings.Trim(oldName, "/")
239 | newName := newParentName
240 | if strings.Contains(oldNameTrim, "/") {
241 | // has child dirs
242 | newName = strings.Replace(oldName, oldParentName, newParentName, 1)
243 | }
244 |
245 | log.Debug("walkDir", toto.V{"oldParentName": oldParentName, "newParentName": newParentName, "oldName": oldName, "newName": newName, "isDir": m.isDir(oldName)})
246 |
247 | if !m.isDir(oldName) {
248 | src := minio.NewSourceInfo(m.bucketName, strings.TrimPrefix(oldName, "/"), nil)
249 | dst, err := minio.NewDestinationInfo(m.bucketName, strings.TrimPrefix(newName, "/"), nil, nil)
250 | if err != nil {
251 | return log.Error(err, toto.V{"op": "walkDir", "dst": dst})
252 | }
253 | if err := m.client.CopyObject(dst, src); err != nil {
254 | return log.Error(err, toto.V{"op": "walkDir", "old": oldName, "new": newName})
255 | }
256 |
257 | return nil
258 | }
259 |
260 | // is dir, then readdir
261 | minioObj, err := m.OpenFile(ctx, oldName, 0, 777)
262 | if err != nil {
263 | return log.Error(err, toto.V{"op": "OpenFile", "old": oldName, "new": newName})
264 | }
265 | oldFileDirChildren, err := minioObj.Readdir(-1)
266 | if err != nil {
267 | return err
268 | }
269 | for _, child := range oldFileDirChildren {
270 | log.Debug("walkDir oldFileDirChildren", toto.V{"op": "walkDir", "oldName": oldName, "child": child.Name(), "len": len(oldFileDirChildren)})
271 | if err := m.walkDir(ctx, oldName, newName, path.Join(oldName, child.Name())); err != nil {
272 | return err
273 | }
274 | }
275 | return nil
276 | }
277 | func (m *minioFileSystem) isDir(name string) bool {
278 | if !strings.HasSuffix(name, "/") {
279 | name = name + "/"
280 | }
281 |
282 | // cache result
283 | isDirPtr := getCacheIsDir(name)
284 | if isDirPtr != nil {
285 | return *isDirPtr
286 | }
287 |
288 | childrenCount := 0
289 | for obj := range m.client.ListObjectsV2(m.bucketName, name, false, nil) {
290 | if obj.Err != nil {
291 | _ = log.Error(obj.Err)
292 | return false
293 | }
294 | childrenCount++
295 | }
296 |
297 | log.Trace("isDir", toto.V{"name": name, "childrenCount": childrenCount})
298 |
299 | if childrenCount <= 0 {
300 | // not dir, not exist
301 |
302 | //double check dir, if it contains hidden .mindavkeep file
303 | _, err := m.client.StatObject(m.bucketName, path.Join(name, KEEP_FILE_NAME), minio.StatObjectOptions{})
304 | if err != nil {
305 | // not dir or not exist
306 | return cacheIsDir(name, false)
307 | }
308 |
309 | // empty dir
310 | return cacheIsDir(name, true)
311 | } else {
312 | // not empty dir
313 | return cacheIsDir(name, true)
314 | }
315 | }
316 | func cacheIsDir(name string, isDir bool) (_isDir bool) {
317 | cache.Forever(isDirCacheKey(name), isDir)
318 | return isDir
319 | }
320 | func getCacheIsDir(name string) (isDirPtr *bool) {
321 | if cache.Has(isDirCacheKey(name)) {
322 | isDir := cache.Get(isDirCacheKey(name)).(bool)
323 | return &isDir
324 | }
325 | return nil
326 | }
327 | func deleteCacheIsDir(name string) {
328 | cache.Forget(isDirCacheKey(name))
329 | }
330 | func isDirCacheKey(name string) string {
331 | const CACHE_KEY_ISDIR = "mindav_isdir_%s"
332 | return fmt.Sprintf(CACHE_KEY_ISDIR, name)
333 | }
334 |
--------------------------------------------------------------------------------
/app/logics/mindav/driver/miniofs/webdav_file.go:
--------------------------------------------------------------------------------
1 | package miniofs
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/minio/minio-go/v6"
7 | "github.com/totoval/framework/config"
8 | "github.com/totoval/framework/helpers/debug"
9 | "github.com/totoval/framework/helpers/hash"
10 | "github.com/totoval/framework/helpers/log"
11 | "github.com/totoval/framework/helpers/toto"
12 | "io"
13 | "os"
14 | "path"
15 | "strings"
16 | )
17 |
18 | type file struct {
19 | m *minioFileSystem
20 | *minio.Object
21 | name string
22 | }
23 |
24 | func (mo *file) Stat() (os.FileInfo, error) {
25 | log.Trace("file stat", toto.V{"name": mo.name})
26 | return mo.m.Stat(context.Background(), mo.name)
27 | }
28 | func (mo *file) ReadFrom(r io.Reader) (n int64, err error) {
29 |
30 | // memory mode
31 | if config.GetBool("webdav.memory_upload_mode") {
32 | n, err = mo.m.client.PutObject(mo.m.bucketName, strings.TrimPrefix(mo.name, "/"), r, -1, minio.PutObjectOptions{ContentType: "application/octet-stream"})
33 | if err != nil {
34 | return 0, log.Error(err, toto.V{"op": "ReadFrom", "name": mo.name})
35 | }
36 | fmt.Println("Successfully uploaded bytes: ", n)
37 | return n, nil
38 | }
39 |
40 | // file mode
41 | tmpFilePath := path.Join(mo.m.uploadTmpPath, hash.Md5(mo.name))
42 | f, err := os.Create(tmpFilePath)
43 | if err != nil {
44 | return 0, err
45 | }
46 | defer f.Close()
47 | defer func(p string) {
48 | err = os.RemoveAll(p)
49 | if err != nil {
50 | _ = log.Error(err, toto.V{"op": "upload", "name": mo.name, "tempName": p})
51 | }
52 | }(tmpFilePath)
53 |
54 | buf := make([]byte, 1024)
55 | for {
56 | // read a chunk
57 | n, err := r.Read(buf)
58 | if err != nil && err != io.EOF {
59 | return 0, err
60 | }
61 | if n == 0 {
62 | break
63 | }
64 |
65 | // write a chunk
66 | if _, err := f.Write(buf[:n]); err != nil {
67 | return 0, err
68 | }
69 | }
70 | n, err = mo.m.client.FPutObject(mo.m.bucketName, strings.TrimPrefix(mo.name, "/"), tmpFilePath, minio.PutObjectOptions{ContentType: "application/octet-stream"})
71 | if err != nil {
72 | return 0, log.Error(err, toto.V{"op": "ReadFrom", "name": mo.name})
73 | }
74 |
75 | log.Trace(hash.Md5(mo.name), toto.V{"op": "upload", "name": mo.name})
76 |
77 | fmt.Println("Successfully uploaded bytes: ", n)
78 | return n, nil
79 | }
80 | func (mo *file) Write(p []byte) (n int, err error) {
81 | debug.DD(p)
82 | return len(p), nil // useless
83 | }
84 |
85 | func (mo *file) Readdir(count int) (fileInfoList []os.FileInfo, err error) {
86 | log.Trace("file readDir", toto.V{"name": mo.name})
87 |
88 | name, err := clearName(mo.name)
89 | if err != nil {
90 | return nil, err
91 | }
92 |
93 | if name != "" {
94 | if !strings.HasSuffix(name, "/") {
95 | name = name + "/"
96 | }
97 | }
98 |
99 | // Create a done channel to control 'ListObjects' go routine.
100 | doneCh := make(chan struct{})
101 |
102 | // Indicate to our routine to exit cleanly upon return.
103 | defer close(doneCh)
104 | // List all objects from a bucket-name with a matching prefix.
105 | for object := range mo.m.client.ListObjectsV2(mo.m.bucketName, name, false, doneCh) {
106 | err = object.Err
107 | if err != nil {
108 | fmt.Println(object.Err)
109 | // return
110 | break
111 | }
112 |
113 | if object.StorageClass == "" && object.ETag == "" && object.Size == 0 {
114 | object.ContentType = "inode/directory"
115 | }
116 |
117 | fileInfoList = append(fileInfoList, &fileInfo{object})
118 | }
119 |
120 | return fileInfoList, err
121 | }
122 |
--------------------------------------------------------------------------------
/app/logics/mindav/filesystem.go:
--------------------------------------------------------------------------------
1 | package mindav
2 |
3 | import (
4 | "github.com/totoval/framework/config"
5 | "golang.org/x/net/webdav"
6 | "totoval/app/logics/mindav/driver/miniofs"
7 | // "totoval/app/logics/mindav/driver/miniofs"
8 | )
9 |
10 | func fileSystem() webdav.FileSystem {
11 | switch config.GetString("webdav.driver") {
12 | case "memory":
13 | return webdav.NewMemFS()
14 | case "file":
15 | return webdav.Dir(config.GetString("webdav.filesystems.file.base_path"))
16 | case "minio":
17 | return miniofs.New(config.GetString("webdav.filesystems.minio.bucket"), config.GetString("webdav.filesystems.minio.location"))
18 | default:
19 | panic("please set a filesystem in the config")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/logics/mindav/initialize.go:
--------------------------------------------------------------------------------
1 | package mindav
2 |
3 | import (
4 | "github.com/totoval/framework/config"
5 | "golang.org/x/net/webdav"
6 | )
7 |
8 | var mindavHandler webdav.Handler
9 |
10 | func Initialize() {
11 |
12 | uri := "/v1" + config.GetString("webdav.base_url")
13 | mindavHandler = webdav.Handler{
14 | Prefix: uri,
15 | FileSystem: fileSystem(),
16 | LockSystem: webdav.NewMemLS(),
17 | }
18 | }
19 |
20 | func Handler() *webdav.Handler {
21 | return &mindavHandler
22 | }
23 |
--------------------------------------------------------------------------------
/app/models/failed_queue.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/totoval/framework/helpers/zone"
5 | "github.com/totoval/framework/request"
6 |
7 | "github.com/totoval/framework/helpers/m"
8 | "github.com/totoval/framework/helpers/pb"
9 | "github.com/totoval/framework/model"
10 | "github.com/totoval/framework/queue"
11 | message "github.com/totoval/framework/queue/protocol_buffers"
12 | )
13 |
14 | type FailedQueue struct {
15 | ID *uint `gorm:"column:failed_queue_id;primary_key;auto_increment"`
16 |
17 | Hash *string `gorm:"column:failed_queue_hash;type:varchar(100);unique_index;not null"`
18 | Topic *string `gorm:"column:failed_queue_topic_name;type:varchar(100);not null"`
19 | Channel *string `gorm:"column:failed_queue_channel_name;type:varchar(100);not null"`
20 | DataProto *[]byte `gorm:"column:failed_queue_data;type:varbinary"`
21 | PushedAt *zone.Time `gorm:"column:failed_queue_pushed_at;not null"`
22 | Delay *zone.Duration `gorm:"column:failed_queue_delay;type:bigint;not null"`
23 | Retries *uint32 `gorm:"column:failed_queue_retries;type:integer unsigned;not null"`
24 | Tried *uint32 `gorm:"column:failed_queue_tried;type:integer unsigned;not null"`
25 | Err *string `gorm:"column:failed_queue_err;size:65535"`
26 |
27 | CreatedAt *zone.Time `gorm:"column:failed_queue_created_at"`
28 | UpdatedAt zone.Time `gorm:"column:failed_queue_updated_at"`
29 | DeletedAt *zone.Time `gorm:"column:failed_queue_deleted_at"`
30 | model.BaseModel
31 | }
32 |
33 | func (fq *FailedQueue) TableName() string {
34 | return fq.SetTableName("failed_queue")
35 | }
36 | func (fq *FailedQueue) RetryTopic() string {
37 | return *fq.Topic
38 | }
39 |
40 | func (fq *FailedQueue) RetryChannel() string {
41 | return *fq.Channel
42 | }
43 |
44 | func (fq *FailedQueue) RetryRetries() uint32 {
45 | return *fq.Retries
46 | }
47 |
48 | func (fq *FailedQueue) RetryDelay() zone.Duration {
49 | return *fq.Delay
50 | }
51 |
52 | func (fq *FailedQueue) RetryParamProtoBytes() []byte {
53 | return *fq.DataProto
54 | }
55 | func (fq *FailedQueue) RetryHash() string {
56 | return *fq.Hash
57 | }
58 |
59 | func (fq *FailedQueue) Default() interface{} {
60 | return FailedQueue{}
61 | }
62 |
63 | func (fq *FailedQueue) FailedToDatabase(topicName string, channelName string, msg *message.Message, errStr string) error {
64 | _fq := FailedQueue{
65 | Topic: &topicName,
66 | Channel: &channelName,
67 | Hash: &msg.Hash,
68 | DataProto: &msg.Param,
69 | PushedAt: pb.TimestampConvert(msg.PushedAt),
70 | Delay: pb.DurationConvert(msg.Delay),
71 | Retries: &msg.Retries,
72 | Tried: &msg.Tried,
73 | Err: &errStr,
74 | }
75 | return m.H().Create(&_fq)
76 | }
77 |
78 | //type retryError byte
79 | //
80 | //const (
81 | // RETRY_NOT_FOUND retryError = iota
82 | // RETRY_DELETE_FAILED
83 | // RETRY_PUSH_FAILED
84 | //)
85 | //
86 | //func (r retryError) Error() string {
87 | // switch r {
88 | // case RETRY_NOT_FOUND:
89 | // return "retry queue not found"
90 | // case RETRY_DELETE_FAILED:
91 | // return "retry queue delete failed"
92 | // case RETRY_PUSH_FAILED:
93 | // default:
94 | // return "retry queue repush failed"
95 | // }
96 | // return "retry error parse error"
97 | //}
98 | func (fq *FailedQueue) FailedQueueById(id uint) (failedQueuerPtr queue.FailedQueuer, err error) {
99 | _fq := FailedQueue{ID: &id}
100 | if err := m.H().First(&_fq, false); err != nil {
101 | return nil, err
102 | }
103 | return &_fq, nil
104 | }
105 | func (fq *FailedQueue) DeleteQueueById(id uint) error {
106 | _fq := FailedQueue{ID: &id}
107 | if err := m.H().Delete(&_fq, true); err != nil {
108 | return err
109 | }
110 | return nil
111 | }
112 |
113 | func (fq *FailedQueue) DeleteAll() error {
114 | //@todo need to be down for queue flush
115 | panic("need implements")
116 | }
117 |
118 | func (fq *FailedQueue) ObjArr(filterArr []model.Filter, sortArr []model.Sort, limit int, withTrashed bool) (interface{}, error) {
119 | var outArr []FailedQueue
120 | if err := m.H().Q(filterArr, sortArr, limit, withTrashed).Find(&outArr).Error; err != nil {
121 | return nil, err
122 | }
123 | return outArr, nil
124 | }
125 | func (fq *FailedQueue) ObjArrPaginate(c *request.Context, perPage uint, filterArr []model.Filter, sortArr []model.Sort, limit int, withTrashed bool) (pagination model.Pagination, err error) {
126 | var outArr []FailedQueue
127 | filter := model.Model(*m.H().Q(filterArr, sortArr, limit, withTrashed))
128 | return filter.Paginate(&outArr, c, perPage)
129 | }
130 |
--------------------------------------------------------------------------------
/app/models/user.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/totoval/framework/helpers/m"
5 | "github.com/totoval/framework/helpers/ptr"
6 | "github.com/totoval/framework/helpers/zone"
7 | "github.com/totoval/framework/model"
8 | )
9 |
10 | type User struct {
11 | ID *uint `gorm:"column:user_id;primary_key;auto_increment"`
12 | Name *string `gorm:"column:user_name;type:varchar(100)"`
13 | Email *string `gorm:"column:user_email;type:varchar(100);unique_index;not null"`
14 | Password *string `gorm:"column:user_password;type:varchar(100);not null"`
15 | CreatedAt *zone.Time `gorm:"column:user_created_at"`
16 | UpdatedAt zone.Time `gorm:"column:user_updated_at"`
17 | DeletedAt *zone.Time `gorm:"column:user_deleted_at"`
18 | model.BaseModel
19 | }
20 |
21 | func (user *User) TableName() string {
22 | return user.SetTableName("user")
23 | }
24 |
25 | func (user *User) Default() interface{} {
26 | return User{}
27 | }
28 |
29 | func (user *User) Scan(userId uint) error {
30 | newUser := User{
31 | ID: ptr.Uint(userId),
32 | }
33 | if err := m.H().First(&newUser, false); err != nil {
34 | return err
35 | }
36 | *user = newUser
37 | return nil
38 | }
39 | func (user *User) Value() interface{} {
40 | return user
41 | }
42 |
43 | func (user *User) User() *User {
44 | //model.DB().Where("user_id = ?", 1).Find(user)
45 | return user
46 | }
47 |
48 | func (user *User) ObjArr(filterArr []model.Filter, sortArr []model.Sort, limit int, withTrashed bool) (interface{}, error) {
49 | var outArr []User
50 | if err := m.H().Q(filterArr, sortArr, limit, withTrashed).Find(&outArr).Error; err != nil {
51 | return nil, err
52 | }
53 | return outArr, nil
54 | }
55 | func (user *User) ObjArrPaginate(c model.Context, perPage uint, filterArr []model.Filter, sortArr []model.Sort, limit int, withTrashed bool) (pagination model.Pagination, err error) {
56 | var outArr []User
57 | filter := model.Model(*m.H().Q(filterArr, sortArr, limit, withTrashed))
58 | return filter.Paginate(&outArr, c, perPage)
59 | }
60 |
--------------------------------------------------------------------------------
/app/models/user_affiliation.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "math"
7 |
8 | "github.com/jinzhu/gorm"
9 |
10 | "github.com/totoval/framework/helpers/debug"
11 | "github.com/totoval/framework/helpers/ptr"
12 | "github.com/totoval/framework/helpers/zone"
13 | "github.com/totoval/framework/model/helper"
14 |
15 | "github.com/totoval/framework/helpers/m"
16 | "github.com/totoval/framework/model"
17 | )
18 |
19 | const AFFILIATION_CODE_LENGTH uint = 6
20 |
21 | type UserAffiliation struct {
22 | UserID *uint `gorm:"column:user_id;primary_key;type:int unsigned"`
23 | Code *string `gorm:"column:uaff_code;type:varchar(32);unique_index;not null"`
24 | FromCode *string `gorm:"column:uaff_from_code;type:varchar(32)"`
25 | Root *uint `gorm:"column:uaff_root_id;type:int unsigned"`
26 | Parent *uint `gorm:"column:uaff_parent_id;type:int unsigned"`
27 | Left *uint `gorm:"column:uaff_left_id;type:int unsigned;not null"`
28 | Right *uint `gorm:"column:uaff_right_id;type:int unsigned;not null"`
29 | Level *uint `gorm:"column:uaff_level;type:int unsigned;not null"`
30 | CreatedAt *zone.Time `gorm:"column:user_created_at"`
31 | UpdatedAt zone.Time `gorm:"column:user_updated_at"`
32 | DeletedAt *zone.Time `gorm:"column:user_deleted_at"`
33 | model.BaseModel
34 | }
35 |
36 | func (uaff *UserAffiliation) TableName() string {
37 | return uaff.SetTableName("user_affiliation")
38 | }
39 |
40 | func (uaff *UserAffiliation) Default() interface{} {
41 | return UserAffiliation{}
42 | }
43 |
44 | func (uaff *UserAffiliation) generateCode(user *User) (string, error) {
45 | if float64(*user.ID) > math.Pow(16, float64(AFFILIATION_CODE_LENGTH)) {
46 | return "", errors.New("userID excceed the max affiliation length")
47 | }
48 |
49 | affiliationLen := fmt.Sprintf("%d", AFFILIATION_CODE_LENGTH)
50 | return fmt.Sprintf("%0"+affiliationLen+"x", *user.ID), nil
51 | }
52 |
53 | func (uaff *UserAffiliation) InsertNode(user *User, fromCode ...string) error {
54 | if user.ID == nil {
55 | return errors.New("user must be existed")
56 | }
57 |
58 | var fromCodePtr *string
59 | if len(fromCode) > 0 {
60 | fromCodePtr = &fromCode[0]
61 | }
62 |
63 | // insert tree node
64 | m.Transaction(func(TransactionHelper *helper.Helper) {
65 | // define affiliation to be inserting
66 | code, err := uaff.generateCode(user)
67 | if err != nil {
68 | panic(err)
69 | }
70 | current := UserAffiliation{
71 | UserID: user.ID,
72 | Code: &code,
73 | FromCode: fromCodePtr,
74 | }
75 |
76 | // new tree
77 | if current.FromCode == nil {
78 | // no parent
79 | current.Root = current.UserID
80 | current.Parent = nil
81 | current.Level = ptr.Uint(1)
82 | current.Left = ptr.Uint(1)
83 | current.Right = ptr.Uint(2)
84 | if err := TransactionHelper.Create(¤t); err != nil {
85 | panic(err)
86 | }
87 | return
88 | }
89 |
90 | // existed tree
91 | // lock table
92 | //@todo switch the DB sql by DB driver
93 | TransactionHelper.DB().Raw("LOCK TABLES ? WRITE", uaff.TableName())
94 | // unlock table
95 | defer TransactionHelper.DB().Raw("UNLOCK TABLES")
96 |
97 | // 1. get parent node
98 | parent := UserAffiliation{
99 | Code: fromCodePtr,
100 | }
101 | if !TransactionHelper.Exist(&parent, false) {
102 | panic(errors.New("affiliation code not found"))
103 | }
104 |
105 | //@todo 2. this.level = parent.level + 1, this.root = parent.root
106 | // current.Root = parent.Root
107 | // current.Parent = parent.UserID
108 | // current.Level = ptr.Uint(*parent.Level + 1)
109 |
110 | //@todo 3. update other nodes
111 | //@todo 3.1 update left: if other.root == parent.root && other.id != this.id && other.left >= parent.right, other.left += 2
112 | if err := TransactionHelper.Q([]model.Filter{
113 | {Key: "uaff_root_id", Op: "=", Val: parent.Root},
114 | {Key: "user_id", Op: "!=", Val: current.UserID},
115 | {Key: "uaff_left_id", Op: ">=", Val: parent.Right},
116 | }, []model.Sort{}, 0, false).Model(UserAffiliation{}).UpdateColumn("uaff_left_id", gorm.Expr("uaff_left_id + ?", 2)).Error; err != nil {
117 | panic(err)
118 | }
119 | //@todo 3.2 update right: if other.root == parent.root && other.id != this.id && other.right >= parent.right, other.right += 2 , here must consider should parent.right+2
120 | if err := TransactionHelper.Q([]model.Filter{
121 | {Key: "uaff_root_id", Op: "=", Val: parent.Root},
122 | {Key: "user_id", Op: "!=", Val: current.UserID},
123 | {Key: "uaff_right_id", Op: ">=", Val: parent.Right},
124 | }, []model.Sort{}, 0, false).Model(UserAffiliation{}).UpdateColumn("uaff_right_id", gorm.Expr("uaff_right_id + ?", 2)).Error; err != nil {
125 | panic(err)
126 | }
127 |
128 | current.Root = parent.Root
129 | current.Parent = parent.UserID
130 | current.Level = ptr.Uint(*parent.Level + 1)
131 |
132 | current.Left = parent.Right
133 | current.Right = ptr.Uint(*parent.Right + 1)
134 | if err := TransactionHelper.Create(¤t); err != nil {
135 | panic(err)
136 | }
137 | return
138 |
139 | }, 1)
140 | return nil
141 |
142 | //@todo 3. update other nodes
143 | //@todo 3.1 update left: if other.root == parent.root && other.id != this.id && other.left >= parent.right, other.left += 2
144 | //@todo 3.2 update right: if other.root == parent.root && other.id != this.id && other.right >= parent.right, other.right += 2 , here must consider should parent.right+2
145 |
146 | //@todo 4. this.left = parent.right - 2, this.right = parent.right -1
147 |
148 | //@todo lock table
149 | }
150 |
151 | func (uaff *UserAffiliation) Tree(rootID uint) ([]UserAffiliation, error) {
152 | root := UserAffiliation{
153 | UserID: &rootID,
154 | }
155 | if err := m.H().First(&root, false); err != nil {
156 | return []UserAffiliation{}, err
157 | }
158 |
159 | nodes, err := uaff.ObjArr([]model.Filter{
160 | {Key: "uaff_root_id", Op: "=", Val: root.UserID},
161 | {Key: "uaff_left_id", Op: ">", Val: root.Left},
162 | {Key: "uaff_right_id", Op: "<", Val: root.Right},
163 | }, []model.Sort{
164 | {Key: "uaff_left_id", Direction: model.ASC},
165 | }, 0, false)
166 |
167 | if err != nil {
168 | return []UserAffiliation{}, err
169 | }
170 |
171 | return nodes.([]UserAffiliation), nil
172 | }
173 | func (uaff *UserAffiliation) CountByParent(parentID uint) (uint, error) {
174 | parent := UserAffiliation{
175 | UserID: &parentID,
176 | }
177 | if err := m.H().First(&parent, false); err != nil {
178 | return 0, err
179 | }
180 |
181 | return (*parent.Right - 1 - *parent.Left) / 2, nil
182 | }
183 | func (uaff *UserAffiliation) TreeByParent(parentID uint) ([]UserAffiliation, error) {
184 | parent := UserAffiliation{
185 | UserID: &parentID,
186 | }
187 | if err := m.H().First(&parent, false); err != nil {
188 | return []UserAffiliation{}, err
189 | }
190 |
191 | nodes, err := uaff.ObjArr([]model.Filter{
192 | {Key: "uaff_root_id", Op: "=", Val: parent.Root},
193 | {Key: "uaff_left_id", Op: ">", Val: parent.Left},
194 | {Key: "uaff_right_id", Op: "<", Val: parent.Right},
195 | }, []model.Sort{
196 | {Key: "uaff_left_id", Direction: model.ASC},
197 | }, 0, false)
198 |
199 | if err != nil {
200 | return []UserAffiliation{}, err
201 | }
202 |
203 | return nodes.([]UserAffiliation), nil
204 | }
205 |
206 | type Tree struct {
207 | ID uint `json:"id"`
208 | Name string `json:"name"`
209 | Value string `json:"value"`
210 | Children []Tree `json:"children"`
211 | }
212 |
213 | func (uaff *UserAffiliation) All() Tree {
214 | rootNodes, err := uaff.ObjArr([]model.Filter{
215 | {Key: "uaff_parent_id", Op: "is_null"},
216 | }, []model.Sort{
217 | {Key: "uaff_left_id", Direction: model.ASC},
218 | }, 0, false)
219 | if err != nil {
220 | return Tree{}
221 | }
222 |
223 | r := Tree{ID: 0, Children: []Tree{}, Name: "root", Value: "root value"}
224 | for _, val := range rootNodes.([]UserAffiliation) {
225 | current := Tree{ID: *val.UserID, Children: []Tree{}, Name: *val.Code, Value: *val.Code}
226 | nodes, err := uaff.Tree(*val.UserID)
227 | if err != nil {
228 | debug.Dump(err)
229 | continue
230 | }
231 |
232 | current.Children = r.recursiveCombineTree(current, 1, nodes)
233 |
234 | r.Children = append(r.Children, current)
235 | }
236 |
237 | return r
238 | }
239 |
240 | func (t *Tree) recursiveCombineTree(current Tree, level uint, nodes []UserAffiliation) []Tree {
241 | for _, uaff := range nodes {
242 | if *uaff.Level < level+1 {
243 | continue
244 | }
245 |
246 | if *uaff.Level > level+1 {
247 | continue
248 | }
249 |
250 | if current.ID == *uaff.Parent {
251 | _current := Tree{
252 | ID: *uaff.UserID,
253 | Children: []Tree{},
254 | Name: *uaff.Code,
255 | Value: *uaff.Code,
256 | }
257 | _current.Children = t.recursiveCombineTree(_current, level+1, nodes)
258 |
259 | current.Children = append(current.Children, _current)
260 | continue
261 | }
262 | }
263 |
264 | return current.Children
265 | }
266 |
267 | func (uaff *UserAffiliation) ObjArr(filterArr []model.Filter, sortArr []model.Sort, limit int, withTrashed bool) (interface{}, error) {
268 | var outArr []UserAffiliation
269 | if err := m.H().Q(filterArr, sortArr, limit, withTrashed).Find(&outArr).Error; err != nil {
270 | return nil, err
271 | }
272 | return outArr, nil
273 | }
274 | func (uaff *UserAffiliation) ObjArrPaginate(c model.Context, perPage uint, filterArr []model.Filter, sortArr []model.Sort, limit int, withTrashed bool) (pagination model.Pagination, err error) {
275 | var outArr []UserAffiliation
276 | filter := model.Model(*m.H().Q(filterArr, sortArr, limit, withTrashed))
277 | return filter.Paginate(&outArr, c, perPage)
278 | }
279 |
--------------------------------------------------------------------------------
/app/models/user_affiliation_test.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "math/rand"
5 | "testing"
6 | "time"
7 |
8 | "github.com/totoval/framework/helpers/debug"
9 |
10 | "github.com/totoval/framework/helpers/str"
11 |
12 | "totoval/resources/lang"
13 |
14 | "github.com/totoval/framework/database"
15 |
16 | "github.com/totoval/framework/cache"
17 |
18 | "totoval/config"
19 |
20 | "github.com/totoval/framework/helpers/ptr"
21 |
22 | "github.com/totoval/framework/helpers/m"
23 | )
24 |
25 | func init() {
26 | config.Initialize()
27 | cache.Initialize()
28 | database.Initialize()
29 | m.Initialize()
30 | lang.Initialize() // an translation must contains resources/lang/xx.json file (then a resources/lang/validation_translator/xx.go)
31 | }
32 |
33 | func TestUserAffiliation_InsertNode(t *testing.T) {
34 | // add root user
35 | root := addUser()
36 | rootUaff := addAffiliation(root)
37 |
38 | randUaff := rootUaff
39 | rand.Seed(time.Now().Unix())
40 |
41 | //add invitation
42 | for i := 0; i < 20; i++ {
43 | u := addUser()
44 | var uaff UserAffiliation
45 |
46 | debug.Dump(randUaff.Code)
47 | if randUaff.Code != nil {
48 | uaff = addAffiliation(u, *randUaff.Code)
49 | } else {
50 | uaff = addAffiliation(u)
51 | }
52 | if rand.Intn(10) > 5 {
53 | randUaff = uaff
54 | }
55 | }
56 | }
57 |
58 | func TestUserAffiliation_All(t *testing.T) {
59 | // dump tree
60 | var uaff UserAffiliation
61 | debug.Dump(uaff.All())
62 | }
63 |
64 | func addUser() User {
65 | user := User{
66 | Email: ptr.String(str.RandString(10) + "@zhigui.com"),
67 | Password: ptr.String(str.RandString(20)),
68 | }
69 | if err := m.H().Create(&user); err != nil {
70 | panic(err)
71 | }
72 |
73 | return user
74 | }
75 | func addAffiliation(user User, fromCode ...string) UserAffiliation {
76 | uaffPtr := &UserAffiliation{
77 | UserID: user.ID,
78 | }
79 | if err := uaffPtr.InsertNode(&user, fromCode...); err != nil {
80 | panic(err)
81 | }
82 |
83 | if err := m.H().First(uaffPtr, false); err != nil {
84 | panic(err)
85 | }
86 |
87 | return *uaffPtr
88 | }
89 |
--------------------------------------------------------------------------------
/app/policies/user_policy.go:
--------------------------------------------------------------------------------
1 | package policies
2 |
3 | import (
4 | "strconv"
5 |
6 | "github.com/totoval/framework/auth"
7 | "github.com/totoval/framework/helpers/debug"
8 | "totoval/app/models"
9 | )
10 |
11 | type userPolicy struct {
12 | }
13 |
14 | func NewUserPolicy() *userPolicy {
15 | return &userPolicy{}
16 | }
17 |
18 | func (up *userPolicy) Before(IUser auth.IUser, routeParamMap map[string]string) *bool {
19 | return nil
20 | }
21 | func (up *userPolicy) Create(IUser auth.IUser, routeParamMap map[string]string) bool {
22 | return false
23 | }
24 | func (up *userPolicy) Update(IUser auth.IUser, routeParamMap map[string]string) bool {
25 | return true
26 | }
27 | func (up *userPolicy) Delete(IUser auth.IUser, routeParamMap map[string]string) bool {
28 | return true
29 | }
30 | func (up *userPolicy) ForceDelete(IUser auth.IUser, routeParamMap map[string]string) bool {
31 | return true
32 | }
33 | func (up *userPolicy) View(IUser auth.IUser, routeParamMap map[string]string) bool {
34 | // get current user
35 | currentUser := IUser.Value().(*models.User)
36 | debug.Dump(currentUser, routeParamMap)
37 |
38 | // if use Authorize func, routeParamMap is nil
39 | if routeParamMap == nil {
40 | return true
41 | }
42 |
43 | // get param user
44 | userIdStr, ok := routeParamMap["userId"]
45 | if !ok {
46 | return false
47 | }
48 | userIdUint, err := strconv.ParseUint(userIdStr, 10, 64)
49 | if err != nil {
50 | return false
51 | }
52 |
53 | if *currentUser.ID != uint(userIdUint) {
54 | return false
55 | }
56 |
57 | return true
58 | }
59 | func (up *userPolicy) Restore(IUser auth.IUser, routeParamMap map[string]string) bool {
60 | return true
61 | }
62 |
--------------------------------------------------------------------------------
/artisan.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/totoval/framework/graceful"
7 | "github.com/totoval/framework/helpers/log"
8 | "github.com/totoval/framework/sentry"
9 |
10 | "totoval/bootstrap"
11 |
12 | "github.com/urfave/cli"
13 |
14 | "github.com/totoval/framework/console"
15 |
16 | "totoval/database/migrations"
17 |
18 | "github.com/totoval/framework/cmd"
19 | command_queue "github.com/totoval/framework/cmd/commands/queue"
20 | "github.com/totoval/framework/cmd/commands/schedule"
21 |
22 | app_schedule "totoval/app/console"
23 |
24 | "totoval/app/console/commands"
25 | )
26 |
27 | func init() {
28 | bootstrap.Initialize()
29 |
30 | migrations.Initialize()
31 | command_queue.Initialize()
32 | commands.Initialize()
33 | schedule.Initialize()
34 |
35 | app_schedule.Schedule(cmd.NewSchedule())
36 | }
37 |
38 | func main() {
39 | cliServe()
40 | }
41 |
42 | func cliServe() {
43 | app := cli.NewApp()
44 | app.Name = "artisan"
45 | app.Usage = "Let's work like an artisan"
46 | app.Version = "0.5.5"
47 |
48 | app.Commands = cmd.List()
49 |
50 | app.Action = func(c *cli.Context) error {
51 | console.Println(console.CODE_INFO, "COMMANDS:")
52 |
53 | for _, cate := range app.Categories() {
54 | categoryName := cate.Name
55 | if categoryName == "" {
56 | categoryName = "kernel"
57 | }
58 | console.Println(console.CODE_WARNING, " "+categoryName+":")
59 |
60 | for _, cmds := range cate.Commands {
61 | console.Println(console.CODE_SUCCESS, " "+cmds.Name+" "+console.Sprintf(console.CODE_INFO, "%s", cmds.ArgsUsage)+" "+console.Sprintf(console.CODE_WARNING, "%s", cmds.Usage))
62 | }
63 | }
64 | return nil
65 | }
66 |
67 | if err := app.Run(os.Args); err != nil {
68 | sentry.CaptureError(err)
69 | log.Fatal(err.Error())
70 | }
71 |
72 | // totoval framework shutdown
73 | graceful.ShutDown(true)
74 | }
75 |
--------------------------------------------------------------------------------
/bootstrap/app.go:
--------------------------------------------------------------------------------
1 | package bootstrap
2 |
3 | import (
4 | "github.com/totoval/framework/cache"
5 | "github.com/totoval/framework/helpers/zone"
6 | "github.com/totoval/framework/logs"
7 | "github.com/totoval/framework/sentry"
8 | "github.com/totoval/framework/validator"
9 | "totoval/app/logics/mindav"
10 |
11 | "totoval/config"
12 | "totoval/resources/lang"
13 | )
14 |
15 | func Initialize() {
16 | config.Initialize()
17 | sentry.Initialize()
18 | logs.Initialize()
19 | zone.Initialize()
20 | lang.Initialize() // an translation must contains resources/lang/xx.json file (then a resources/lang/validation_translator/xx.go)
21 | cache.Initialize()
22 | // database.Initialize()
23 | // m.Initialize()
24 | // queue.Initialize()
25 | // jobs.Initialize()
26 | // events.Initialize()
27 | // listeners.Initialize()
28 | mindav.Initialize()
29 |
30 | validator.UpgradeValidatorV8toV9()
31 | }
32 |
--------------------------------------------------------------------------------
/config/app.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | . "github.com/totoval/framework/config"
5 | )
6 |
7 | func init() {
8 | app := make(map[string]interface{})
9 |
10 | app["name"] = Env("APP_NAME", "Totoval")
11 | app["env"] = Env("APP_ENV", "production")
12 | app["debug"] = Env("APP_DEBUG", false)
13 | app["log_level"] = Env("APP_LOG_LEVEL", "trace")
14 | app["port"] = Env("APP_PORT", 8080)
15 | app["timezone"] = "UTC"
16 | app["locale"] = Env("APP_LOCALE", "en")
17 | app["fallback_locale"] = "en"
18 | app["key"] = Env("APP_KEY")
19 | app["cipher"] = "AES-256-CBC"
20 | app["read_timeout_seconds"] = 86400
21 | app["write_timeout_seconds"] = 86400
22 |
23 | Add("app", app)
24 | }
25 |
--------------------------------------------------------------------------------
/config/auth.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | . "github.com/totoval/framework/config"
5 | "totoval/app/models"
6 | )
7 |
8 | func init() {
9 | auth := make(map[string]interface{})
10 |
11 | auth["sign_key"] = Env("AUTH_SIGN_KEY", "sign key")
12 | auth["model_ptr"] = &models.User{} // must be a pointer
13 |
14 | Add("auth", auth)
15 | }
16 |
--------------------------------------------------------------------------------
/config/cache.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | . "github.com/totoval/framework/config"
5 | )
6 |
7 | func init() {
8 | cache := make(map[string]interface{})
9 |
10 | cache["default"] = Env("CACHE_DRIVER", "memory")
11 | cache["stores"] = map[string]interface{}{
12 | "memory": map[string]interface{}{
13 | "driver": "memory",
14 | "default_expiration_minute": 5,
15 | "cleanup_interval_minute": 5,
16 | "prefix": Env("APP_NAME", "totoval").(string) + "_cache_",
17 | },
18 | "redis": map[string]interface{}{
19 | "driver": "redis",
20 | "connection": "cache",
21 | },
22 | }
23 |
24 | Add("cache", cache)
25 | }
26 |
--------------------------------------------------------------------------------
/config/database.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | . "github.com/totoval/framework/config"
5 | )
6 |
7 | func init() {
8 | database := make(map[string]interface{})
9 |
10 | database["default"] = Env("DB_CONNECTION", "mysql")
11 | database["connections"] = map[string]interface{}{
12 | "mysql": map[string]interface{}{
13 | "driver": "mysql",
14 | "host": Env("DB_HOST", "127.0.0.1"),
15 | "port": Env("DB_PORT", "3306"),
16 | "database": Env("DB_DATABASE", ""),
17 | "username": Env("DB_USERNAME", ""),
18 | "password": Env("DB_PASSWORD", ""),
19 | "charset": "utf8mb4",
20 | "collation": "utf8mb4_unicode_ci",
21 | "prefix": Env("DB_PREFIX", ""),
22 | },
23 | }
24 | database["migrations"] = "migrations"
25 | database["max_idle_connections"] = Env("DB_MAX_IDLE_CONNECTIONS", 2) // 2 is the cpu cores
26 | database["max_open_connections"] = Env("DB_MAX_OPEN_CONNECTIONS", 0) // 2 is the cpu cores
27 | database["max_life_seconds"] = Env("DB_MAX_LIFE_SECONDS", 0) // 2 is the cpu cores
28 |
29 | database["redis"] = map[string]interface{}{
30 |
31 | "options": map[string]interface{}{
32 | "prefix": Env("APP_NAME", "totoval").(string) + "_database_",
33 | },
34 |
35 | "default": map[string]interface{}{
36 | "host": Env("REDIS_HOST", "127.0.0.1"),
37 | "port": Env("REDIS_PORT", "6379"),
38 | "password": Env("REDIS_PASSWORD", ""),
39 | "database": Env("REDIS_DB", 0),
40 | },
41 | "cache": map[string]interface{}{
42 | "host": Env("REDIS_HOST", "127.0.0.1"),
43 | "port": Env("REDIS_PORT", "6379"),
44 | "password": Env("REDIS_PASSWORD", ""),
45 | "database": Env("REDIS_CACHE_DB", 1),
46 | },
47 | }
48 |
49 | Add("database", database)
50 | }
51 |
--------------------------------------------------------------------------------
/config/kernel.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/totoval/framework/app"
5 | "github.com/totoval/framework/config"
6 | )
7 |
8 | func Initialize() {
9 | setAppMode()
10 | }
11 |
12 | func setAppMode() {
13 | switch config.GetString("app.env") {
14 | case "production":
15 | app.SetMode(app.ModeProduction)
16 | case "develop":
17 | app.SetMode(app.ModeDevelop)
18 | case "test":
19 | app.SetMode(app.ModeTest)
20 | default:
21 | app.SetMode(app.ModeDevelop)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/config/queue.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | . "github.com/totoval/framework/config"
5 | "totoval/app/models"
6 | )
7 |
8 | func init() {
9 | queue := make(map[string]interface{})
10 |
11 | queue["default"] = Env("QUEUE_CONNECTION", "memory")
12 | queue["connections"] = map[string]interface{}{
13 | "nsq": map[string]interface{}{
14 | "nsqd": []map[string]interface{}{
15 | {
16 | "tcp": map[string]interface{}{
17 | "host": Env("QUEUE_NSQD_TCP_HOST", "127.0.0.1"),
18 | "port": Env("QUEUE_NSQD_TCP_PORT", "4150"),
19 | },
20 | },
21 | },
22 | "nsqlookupd": []map[string]interface{}{
23 | {
24 | "http": map[string]interface{}{
25 | "host": Env("QUEUE_NSQLOOKUPD_HTTP_HOST", "http://127.0.0.1"),
26 | "port": Env("QUEUE_NSQLOOKUPD_HTTP_PORT", "4161"),
27 | },
28 | },
29 | },
30 | },
31 | }
32 |
33 | queue["max_in_flight"] = Env("QUEUE_MAX_IN_FLIGHT", 25)
34 | queue["failed_db_processor_model"] = &models.FailedQueue{}
35 |
36 | Add("queue", queue)
37 | }
38 |
--------------------------------------------------------------------------------
/config/sentry.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | . "github.com/totoval/framework/config"
5 | )
6 |
7 | func init() {
8 | sentry := make(map[string]interface{})
9 |
10 | sentry["enable"] = Env("SENTRY_ENABLE", false)
11 | sentry["host"] = Env("SENTRY_HOST", "app.getsentry.com")
12 | sentry["key"] = Env("SENTRY_KEY", "YOUR-OWN-SENTRY-KEY")
13 | sentry["secret"] = Env("SENTRY_SECRET", "YOUR-OWN-SENTRY-SECRET")
14 | sentry["project"] = Env("SENTRY_PROJECT", "YOUR-OWN-SENTRY-PROJECT")
15 |
16 | Add("sentry", sentry)
17 | }
18 |
--------------------------------------------------------------------------------
/config/user_affiliation.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | . "github.com/totoval/framework/config"
5 | )
6 |
7 | func init() {
8 | userAffiliation := make(map[string]interface{})
9 |
10 | userAffiliation["enable"] = true
11 |
12 | Add("user_affiliation", userAffiliation)
13 | }
14 |
--------------------------------------------------------------------------------
/config/webdav.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | . "github.com/totoval/framework/config"
6 | )
7 |
8 | func init() {
9 | webdav := make(map[string]interface{})
10 |
11 | webdav["driver"] = Env("WEBDAV_DRIVER", "memory") // file, memory, minio
12 |
13 | webdav["filesystems"] = map[string]interface{}{
14 | "file": map[string]interface{}{
15 | "base_path": ".",
16 | },
17 | "minio": map[string]interface{}{
18 | "endpoint": Env("MINIO_ENDPOINT", "play.min.io:9000"),
19 | "bucket": Env("MINIO_BUCKET", "bucket_name"),
20 | "access_key_id": Env("MINIO_ACCESS_KEY_ID", "access_key_id"),
21 | "secret_access_key": Env("MINIO_SECRET_ACCESS_KEY", "secret_access_key"),
22 | "use_ssl": Env("MINIO_USE_SSL", true),
23 | "location": "us-east-1",
24 | },
25 | }
26 | webdav["base_path"] = "." // for "file" filesystem
27 | webdav["supported_folder_depth"] = 10
28 | webdav["base_url"] = "/webdav"
29 | webdav["memory_upload_mode"] = Env("MEMORY_UPLOAD_MODE", false) // If the host has a large memory, then set to `true` could improve upload performance.
30 |
31 | webdav["accounts"] = gin.Accounts{
32 | Env("WEBDAV_USER", "totoval").(string): Env("WEBDAV_PASSWORD", "passw0rd").(string),
33 | }
34 |
35 | Add("webdav", webdav)
36 | }
37 |
--------------------------------------------------------------------------------
/database/migrations/create_failed_queue_table_1556612225.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "github.com/jinzhu/gorm"
5 |
6 | "github.com/totoval/framework/database/migration"
7 | "github.com/totoval/framework/helpers/zone"
8 | "github.com/totoval/framework/model"
9 | )
10 |
11 | func init() {
12 | migration.AddMigrator(&CreateFailedQueueTable1556612225{})
13 | }
14 |
15 | type FailedQueue struct {
16 | ID *uint `gorm:"column:failed_queue_id;primary_key;auto_increment"`
17 |
18 | Hash *string `gorm:"column:failed_queue_hash;type:varchar(100);unique_index;not null"`
19 | Topic *string `gorm:"column:failed_queue_topic_name;type:varchar(100);not null"`
20 | Channel *string `gorm:"column:failed_queue_channel_name;type:varchar(100);not null"`
21 | DataProto *[]byte `gorm:"column:failed_queue_data;type:varbinary(2048)"`
22 | PushedAt *zone.Time `gorm:"column:failed_queue_pushed_at;not null"`
23 | Delay *zone.Duration `gorm:"column:failed_queue_delay;type:bigint unsigned;not null"`
24 | Retries *uint32 `gorm:"column:failed_queue_retries;type:integer unsigned;not null"`
25 | Tried *uint32 `gorm:"column:failed_queue_tried;type:integer unsigned;not null"`
26 | Err *string `gorm:"column:failed_queue_err;size:65535"`
27 |
28 | CreatedAt *zone.Time `gorm:"column:failed_queue_created_at"`
29 | UpdatedAt zone.Time `gorm:"column:failed_queue_updated_at"`
30 | DeletedAt *zone.Time `gorm:"column:failed_queue_deleted_at"`
31 | model.BaseModel
32 | }
33 |
34 | func (fq *FailedQueue) TableName() string {
35 | return fq.SetTableName("failed_queue")
36 | }
37 |
38 | type CreateFailedQueueTable1556612225 struct {
39 | migration.MigratorIdentify
40 | migration.MigrationUtils
41 | }
42 |
43 | func (*CreateFailedQueueTable1556612225) Up(db *gorm.DB) *gorm.DB {
44 | db = db.CreateTable(&FailedQueue{})
45 | return db
46 | }
47 |
48 | func (*CreateFailedQueueTable1556612225) Down(db *gorm.DB) *gorm.DB {
49 | db = db.DropTableIfExists(&FailedQueue{})
50 | return db
51 | }
52 |
--------------------------------------------------------------------------------
/database/migrations/create_user_affiliation_table_1553678539.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "github.com/jinzhu/gorm"
5 |
6 | "github.com/totoval/framework/database/migration"
7 | "github.com/totoval/framework/helpers/zone"
8 | "github.com/totoval/framework/model"
9 | )
10 |
11 | func init() {
12 | migration.AddMigrator(&CreateUserAffiliationTable1553678539{})
13 | }
14 |
15 | type UserAffiliation struct {
16 | UserID *uint `gorm:"column:user_id;primary_key;type:int unsigned"`
17 | Code *string `gorm:"column:uaff_code;type:varchar(32);unique_index;not null"`
18 | FromCode *string `gorm:"column:uaff_from_code;type:varchar(32)"`
19 | Root *uint `gorm:"column:uaff_root_id;type:int unsigned"`
20 | Parent *uint `gorm:"column:uaff_parent_id;type:int unsigned"`
21 | Left *uint `gorm:"column:uaff_left_id;type:int unsigned;not null"`
22 | Right *uint `gorm:"column:uaff_right_id;type:int unsigned;not null"`
23 | Level *uint `gorm:"column:uaff_level;type:int unsigned;not null"`
24 | CreatedAt *zone.Time `gorm:"column:user_created_at"`
25 | UpdatedAt zone.Time `gorm:"column:user_updated_at"`
26 | DeletedAt *zone.Time `gorm:"column:user_deleted_at"`
27 | model.BaseModel
28 | }
29 |
30 | func (uaff *UserAffiliation) TableName() string {
31 | return uaff.SetTableName("user_affiliation")
32 | }
33 |
34 | type CreateUserAffiliationTable1553678539 struct {
35 | migration.MigratorIdentify
36 | migration.MigrationUtils
37 | }
38 |
39 | func (*CreateUserAffiliationTable1553678539) Up(db *gorm.DB) *gorm.DB {
40 | db = db.CreateTable(&UserAffiliation{})
41 | return db
42 | }
43 |
44 | func (*CreateUserAffiliationTable1553678539) Down(db *gorm.DB) *gorm.DB {
45 | db = db.DropTableIfExists(&UserAffiliation{})
46 | return db
47 | }
48 |
--------------------------------------------------------------------------------
/database/migrations/create_user_table_1548750742.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import (
4 | "github.com/jinzhu/gorm"
5 |
6 | "github.com/totoval/framework/database/migration"
7 | "github.com/totoval/framework/helpers/zone"
8 | "github.com/totoval/framework/model"
9 | )
10 |
11 | func init() {
12 | migration.AddMigrator(&CreateUserTable1548750742{})
13 | }
14 |
15 | type User struct {
16 | ID *uint `gorm:"column:user_id;primary_key;auto_increment"`
17 | Name *string `gorm:"column:user_name;type:varchar(100)"` //@cautions struct member must be pointer when member could be null
18 | Email *string `gorm:"column:user_email;type:varchar(100);unique_index;not null"`
19 | Password *string `gorm:"column:user_password;type:varchar(100);not null"`
20 | CreatedAt *zone.Time `gorm:"column:user_created_at"`
21 | UpdatedAt zone.Time `gorm:"column:user_updated_at"`
22 | DeletedAt *zone.Time `gorm:"column:user_deleted_at"`
23 | model.BaseModel
24 | }
25 |
26 | func (u *User) TableName() string {
27 | return u.SetTableName("user")
28 | }
29 |
30 | type CreateUserTable1548750742 struct {
31 | migration.MigratorIdentify
32 | migration.MigrationUtils
33 | }
34 |
35 | func (*CreateUserTable1548750742) Up(db *gorm.DB) *gorm.DB {
36 | db = db.CreateTable(&User{})
37 | return db
38 | }
39 |
40 | func (*CreateUserTable1548750742) Down(db *gorm.DB) *gorm.DB {
41 | db = db.DropTableIfExists(&User{})
42 | return db
43 | }
44 |
--------------------------------------------------------------------------------
/database/migrations/kernel.go:
--------------------------------------------------------------------------------
1 | package migrations
2 |
3 | import "github.com/totoval/framework/cmd/commands/migration"
4 |
5 | func Initialize() {
6 | migration.Initialize()
7 | }
8 |
--------------------------------------------------------------------------------
/database/seeds/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/totoval/mindav/fcd09af481cddcb02b97ea1b19ca3c723d03078d/database/seeds/.gitkeep
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | services:
3 |
4 | minio:
5 | image: minio/minio:latest
6 | expose:
7 | - "9000"
8 | ports:
9 | - "9000:9000"
10 | volumes:
11 | - ./minio/data:/export
12 | environment:
13 | MINIO_ACCESS_KEY: minio
14 | MINIO_SECRET_KEY: miniostorage
15 | command: server /export
16 | restart: always
17 |
18 | mindav:
19 | image: totoval/mindav:latest
20 | depends_on:
21 | - minio
22 | links:
23 | - minio
24 | expose:
25 | - "80"
26 | ports:
27 | - "80:80"
28 | restart: always
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module totoval
2 |
3 | require (
4 | github.com/gin-gonic/gin v1.4.0
5 | github.com/golang/protobuf v1.3.1
6 | github.com/jinzhu/gorm v1.9.2
7 | github.com/minio/minio-go/v6 v6.0.32
8 | github.com/totoval/framework v0.9.3
9 | github.com/urfave/cli v1.20.0
10 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092
11 | )
12 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "github.com/gin-gonic/gin"
6 | "net/http"
7 | "os"
8 | "os/signal"
9 | "syscall"
10 |
11 | c "github.com/totoval/framework/config"
12 | "github.com/totoval/framework/graceful"
13 | "github.com/totoval/framework/helpers/log"
14 | "github.com/totoval/framework/helpers/toto"
15 | "github.com/totoval/framework/helpers/zone"
16 | "github.com/totoval/framework/http/middleware"
17 | "github.com/totoval/framework/request"
18 | "github.com/totoval/framework/sentry"
19 | "totoval/bootstrap"
20 | "totoval/resources/views"
21 | "totoval/routes"
22 | )
23 |
24 | func init() {
25 | bootstrap.Initialize()
26 | }
27 |
28 | // @caution cannot use config methods to get config in init function
29 | func main() {
30 | //j := &jobs.ExampleJob{}
31 | //j.SetParam(&pbs.ExampleJob{Query: "test", PageNumber: 111, ResultPerPage: 222})
32 | ////j.SetDelay(5 * zone.Second)
33 | //err := job.Dispatch(j)
34 | //fmt.Println(err)
35 |
36 | //go hub.On("add-user-affiliation") // go run artisan.go queue:listen add-user-affiliation
37 |
38 | ctx, cancel := context.WithCancel(context.Background())
39 |
40 | quit := make(chan os.Signal, 1)
41 | // kill (no param) default send syscanll.SIGTERM
42 | // kill -2 is syscall.SIGINT
43 | // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
44 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
45 |
46 | go func() {
47 | call := <-quit
48 | log.Info("system call", toto.V{"call": call})
49 | cancel()
50 | }()
51 |
52 | httpServe(ctx)
53 | }
54 |
55 | func httpServe(ctx context.Context) {
56 | r := request.New()
57 |
58 | sentry.Use(r.GinEngine(), false)
59 |
60 | if c.GetBool("app.debug") {
61 | //r.Use(middleware.RequestLogger())
62 | }
63 |
64 | r.RedirectTrailingSlash = false
65 |
66 | if c.GetString("app.env") == "production" {
67 | r.Use(middleware.Logger())
68 | r.Use(middleware.Recovery())
69 | }
70 |
71 | r.Use(middleware.Locale())
72 |
73 | r.UseGin(gin.BasicAuth(c.Get("webdav.accounts").(gin.Accounts)))
74 |
75 | routes.Register(r)
76 |
77 | views.Initialize(r)
78 |
79 | s := &http.Server{
80 | Addr: ":" + c.GetString("app.port"),
81 | Handler: r,
82 | ReadTimeout: zone.Duration(c.GetInt64("app.read_timeout_seconds")) * zone.Second,
83 | WriteTimeout: zone.Duration(c.GetInt64("app.write_timeout_seconds")) * zone.Second,
84 | MaxHeaderBytes: 1 << 20,
85 | }
86 |
87 | go func() {
88 | if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
89 | log.Fatal(err.Error())
90 | }
91 | }()
92 |
93 | <-ctx.Done()
94 |
95 | log.Info("Shutdown Server ...")
96 |
97 | // Wait for interrupt signal to gracefully shutdown the server with
98 | // a timeout of 5 seconds.
99 | _ctx, cancel := context.WithTimeout(ctx, 5*zone.Second)
100 | defer cancel()
101 |
102 | if err := s.Shutdown(_ctx); err != nil {
103 | log.Fatal("Server Shutdown: ", toto.V{"error": err})
104 | }
105 |
106 | // totoval framework shutdown
107 | graceful.ShutDown(false)
108 |
109 | log.Info("Server exiting")
110 | }
111 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/totoval/mindav/fcd09af481cddcb02b97ea1b19ca3c723d03078d/makefile
--------------------------------------------------------------------------------
/readme_assets/37E56D20-FCA7-41FB-B8B2-3B5E390A6DBC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/totoval/mindav/fcd09af481cddcb02b97ea1b19ca3c723d03078d/readme_assets/37E56D20-FCA7-41FB-B8B2-3B5E390A6DBC.png
--------------------------------------------------------------------------------
/readme_assets/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/totoval/mindav/fcd09af481cddcb02b97ea1b19ca3c723d03078d/readme_assets/architecture.png
--------------------------------------------------------------------------------
/resources/lang/en.go:
--------------------------------------------------------------------------------
1 | package lang
2 |
3 | import (
4 | "github.com/totoval/framework/helpers/locale"
5 |
6 | "github.com/totoval/framework/resources/lang"
7 | )
8 |
9 | func init() {
10 | validationTranslation := lang.ValidationTranslation{
11 | Required: "{0} is a required field",
12 | Len: lang.EmbeddedRule{
13 | String: "{0} must be {1} in length",
14 | Numeric: "{0} must be equal to {1}",
15 | Array: "{0} must contain {1}",
16 | },
17 | Min: lang.EmbeddedRule{
18 | String: "{0} must be at least {1} in length",
19 | Numeric: "{0} must be {1} or greater",
20 | Array: "{0} must contain at least {1}",
21 | },
22 | Max: lang.EmbeddedRule{
23 | String: "{0} must be a maximum of {1} in length",
24 | Numeric: "{0} must be {1} or less",
25 | Array: "{0} must contain at maximum {1}",
26 | },
27 | Eq: "{0} is not equal to {1}",
28 | Ne: "{0} should not be equal to {1}",
29 | Lt: lang.EmbeddedRule{
30 | String: "{0} must be less than {1} in length",
31 | Numeric: "{0} must be less than {1}",
32 | Array: "{0} must contain less than {1}",
33 | Datetime: "{0} must be less than the current Date & Time",
34 | },
35 | Lte: lang.EmbeddedRule{
36 | String: "{0} must be at maximum {1} in length",
37 | Numeric: "{0} must be {1} or less",
38 | Array: "{0} must contain at maximum {1}",
39 | Datetime: "{0} must be less than or equal to the current Date & Time",
40 | },
41 | Gt: lang.EmbeddedRule{
42 | String: "{0} must be greater than {1} in length",
43 | Numeric: "{0} must be greater than {1}",
44 | Array: "{0} must contain more than {1}",
45 | Datetime: "{0} must be greater than the current Date & Time",
46 | },
47 | Gte: lang.EmbeddedRule{
48 | String: "{0} must be at least {1} in length",
49 | Numeric: "{0} must be {1} or greater",
50 | Array: "{0} must contain at least {1}",
51 | Datetime: "{0} must be greater than or equal to the current Date & Time",
52 | },
53 | Eqfield: "{0} must be equal to {1}",
54 | Eqcsfield: "{0} must be equal to {1}",
55 | Necsfield: "{0} cannot be equal to {1}",
56 | Gtcsfield: "{0} must be greater than {1}",
57 | Gtecsfield: "{0} must be greater than or equal to {1}",
58 | Ltcsfield: "{0} must be less than {1}",
59 | Ltecsfield: "{0} must be less than or equal to {1}",
60 | Nefield: "{0} cannot be equal to {1}",
61 | Gtfield: "{0} must be greater than {1}",
62 | Gtefield: "{0} must be greater than or equal to {1}",
63 | Ltfield: "{0} must be less than {1}",
64 | Ltefield: "{0} must be less than or equal to {1}",
65 | Alpha: "{0} can only contain alphabetic characters",
66 | Alphanum: "{0} can only contain alphanumeric characters",
67 | Numeric: "{0} must be a valid numeric value",
68 | Number: "{0} must be a valid number",
69 | Hexadecimal: "{0} must be a valid hexadecimal",
70 | Hexcolor: "{0} must be a valid HEX color",
71 | Rgb: "{0} must be a valid RGB color",
72 | Rgba: "{0} must be a valid RGBA color",
73 | Hsl: "{0} must be a valid HSL color",
74 | Hsla: "{0} must be a valid HSLA color",
75 | Email: "{0} must be a valid email address",
76 | Url: "{0} must be a valid URL",
77 | Uri: "{0} must be a valid URI",
78 | Base64: "{0} must be a valid Base64 string",
79 | Base64url: "{0} must contains a valid Base64 URL safe value",
80 | UrnRfc2141: "{0} must contains a valid URN string",
81 | Contains: "{0} must contain the text '{1}'",
82 | Containsany: "{0} must contain at least one of the following characters '{1}'",
83 | Excludes: "{0} cannot contain the text '{1}'",
84 | Excludesall: "{0} cannot contain any of the following characters '{1}'",
85 | Excludesrune: "{0} cannot contain the following '{1}'",
86 | Isbn: "{0} must be a valid ISBN number",
87 | Isbn10: "{0} must be a valid ISBN-10 number",
88 | Isbn13: "{0} must be a valid ISBN-13 number",
89 | Uuid: "{0} must be a valid UUID",
90 | Uuid3: "{0} must be a valid version 3 UUID",
91 | Uuid4: "{0} must be a valid version 4 UUID",
92 | Uuid5: "{0} must be a valid version 5 UUID",
93 | Ascii: "{0} must contain only ascii characters",
94 | Printascii: "{0} must contain only printable ascii characters",
95 | Multibyte: "{0} must contain multibyte characters",
96 | Datauri: "{0} must contain a valid Data URI",
97 | Latitude: "{0} must contain valid latitude coordinates",
98 | Longitude: "{0} must contain a valid longitude coordinates",
99 | Ssn: "{0} must be a valid SSN number",
100 | Ipv4: "{0} must be a valid IPv4 address",
101 | Ipv6: "{0} must be a valid IPv6 address",
102 | Ip: "{0} must be a valid IP address",
103 | Cidr: "{0} must contain a valid CIDR notation",
104 | Cidrv4: "{0} must contain a valid CIDR notation for an IPv4 address",
105 | Cidrv6: "{0} must contain a valid CIDR notation for an IPv6 address",
106 | TcpAddr: "{0} must be a valid TCP address",
107 | Tcp4Addr: "{0} must be a valid IPv4 TCP address",
108 | Tcp6Addr: "{0} must be a valid IPv6 TCP address",
109 | UdpAddr: "{0} must be a valid UDP address",
110 | Udp4Addr: "{0} must be a valid IPv4 UDP address",
111 | Udp6Addr: "{0} must be a valid IPv6 UDP address",
112 | IpAddr: "{0} must be a resolvable IP address",
113 | Ip4Addr: "{0} must be a resolvable IPv4 address",
114 | Ip6Addr: "{0} must be a resolvable IPv6 address",
115 | UnixAddr: "{0} must be a resolvable UNIX address",
116 | Mac: "{0} must contain a valid MAC address",
117 | Unique: "{0} must contain unique values",
118 | Iscolor: "{0} must be a valid color",
119 | Oneof: "{0} must be one of [{1}]",
120 | BtcAddr: "{0} must be a valid bitcoin address",
121 | BtcAddrBech32: "{0} must be a valid bitcoin Bech32 address",
122 | EthAddr: "{0} must be a valid ethereum address",
123 |
124 | PluralRuleMap: map[string]lang.PluralRule{
125 | "character": {
126 | One: "character",
127 | Other: "characters",
128 | },
129 | "item": {
130 | One: "item",
131 | Other: "items",
132 | },
133 | },
134 |
135 | FieldTranslation: lang.ValidationFieldTranslation{
136 | "Email": "email",
137 | },
138 | }
139 | customTranslation := lang.CustomTranslation{
140 | "auth.register.failed_existed": "user register failed, user has been registered before",
141 | "auth.register.failed_system_error": "user register failed, system error",
142 | "auth.register.failed_token_generate_error": "user register failed, token generate failed",
143 |
144 | "auth.login.failed_not_exist": "user login failed, user doesn't exist",
145 | "auth.login.failed_wrong_password": "user login failed, user password incorrect",
146 | "auth.login.failed_token_generate_error": "user login failed, token generate failed",
147 | }
148 |
149 | locale.AddLocale("en", &customTranslation, &validationTranslation)
150 | }
151 |
--------------------------------------------------------------------------------
/resources/lang/kernel.go:
--------------------------------------------------------------------------------
1 | package lang
2 |
3 | func Initialize() {
4 | }
5 |
--------------------------------------------------------------------------------
/resources/lang/zh.go:
--------------------------------------------------------------------------------
1 | package lang
2 |
3 | import (
4 | "github.com/totoval/framework/helpers/locale"
5 |
6 | "github.com/totoval/framework/resources/lang"
7 | )
8 |
9 | func init() {
10 | validationTranslation := lang.ValidationTranslation{
11 | Required: "{0}为必填字段",
12 | Len: lang.EmbeddedRule{
13 | String: "{0}长度必须是{1}",
14 | Numeric: "{0}必须等于{1}",
15 | Array: "{0}必须包含{1}",
16 | },
17 | Min: lang.EmbeddedRule{
18 | String: "{0}长度必须至少为{1}",
19 | Numeric: "{0}最小只能为{1}",
20 | Array: "{0}必须至少包含{1}",
21 | },
22 | Max: lang.EmbeddedRule{
23 | String: "{0}长度不能超过{1}",
24 | Numeric: "{0}必须小于或等于{1}",
25 | Array: "{0}最多只能包含{1}",
26 | },
27 | Eq: "{0}不等于{1}",
28 | Ne: "{0}不能等于{1}",
29 | Lt: lang.EmbeddedRule{
30 | String: "{0}长度必须小于{1}",
31 | Numeric: "{0}必须小于{1}",
32 | Array: "{0}必须包含少于{1}",
33 | Datetime: "{0}必须小于当前日期和时间",
34 | },
35 | Lte: lang.EmbeddedRule{
36 | String: "{0}长度不能超过{1}",
37 | Numeric: "{0}必须小于或等于{1}",
38 | Array: "{0}最多只能包含{1}",
39 | Datetime: "{0}必须小于或等于当前日期和时间",
40 | },
41 | Gt: lang.EmbeddedRule{
42 | String: "{0}长度必须大于{1}",
43 | Numeric: "{0}必须大于{1}",
44 | Array: "{0}必须大于{1}",
45 | Datetime: "{0}必须大于当前日期和时间",
46 | },
47 | Gte: lang.EmbeddedRule{
48 | String: "{0}长度必须至少为{1}",
49 | Numeric: "{0}必须大于或等于{1}",
50 | Array: "{0}必须至少包含{1}",
51 | Datetime: "{0}必须大于或等于当前日期和时间",
52 | },
53 | Eqfield: "{0}必须等于{1}",
54 | Eqcsfield: "{0}必须等于{1}",
55 | Necsfield: "{0}不能等于{1}",
56 | Gtcsfield: "{0}必须大于{1}",
57 | Gtecsfield: "{0}必须大于或等于{1}",
58 | Ltcsfield: "{0}必须小于{1}",
59 | Ltecsfield: "{0}必须小于或等于{1}",
60 | Nefield: "{0}不能等于{1}",
61 | Gtfield: "{0}必须大于{1}",
62 | Gtefield: "{0}必须大于或等于{1}",
63 | Ltfield: "{0}必须小于{1}",
64 | Ltefield: "{0}必须小于或等于{1}",
65 | Alpha: "{0}只能包含字母",
66 | Alphanum: "{0}只能包含字母和数字",
67 | Numeric: "{0}必须是一个有效的数值",
68 | Number: "{0}必须是一个有效的数字",
69 | Hexadecimal: "{0}必须是一个有效的十六进制",
70 | Hexcolor: "{0}必须是一个有效的十六进制颜色",
71 | Rgb: "{0}必须是一个有效的RGB颜色",
72 | Rgba: "{0}必须是一个有效的RGBA颜色",
73 | Hsl: "{0}必须是一个有效的HSL颜色",
74 | Hsla: "{0}必须是一个有效的HSLA颜色",
75 | Email: "{0}必须是一个有效的邮箱",
76 | Url: "{0}必须是一个有效的URL",
77 | Uri: "{0}必须是一个有效的URI",
78 | Base64: "{0}必须是一个有效的Base64字符串",
79 | Base64url: "{0}必须是一个有效的Base64的URL",
80 | UrnRfc2141: "{0}必须是一个有效的URN字符串",
81 | Contains: "{0}必须包含文本'{1}'",
82 | Containsany: "{0}必须包含至少一个以下字符'{1}'",
83 | Excludes: "{0}不能包含文本'{1}'",
84 | Excludesall: "{0}不能包含以下任何字符'{1}'",
85 | Excludesrune: "{0}不能包含'{1}'",
86 | Isbn: "{0}必须是一个有效的ISBN编号",
87 | Isbn10: "{0}必须是一个有效的ISBN-10编号",
88 | Isbn13: "{0}必须是一个有效的ISBN-13编号",
89 | Uuid: "{0}必须是一个有效的UUID",
90 | Uuid3: "{0}必须是一个有效的V3 UUID",
91 | Uuid4: "{0}必须是一个有效的V4 UUID",
92 | Uuid5: "{0}必须是一个有效的V5 UUID",
93 | Ascii: "{0}必须只包含ascii字符",
94 | Printascii: "{0}必须只包含可打印的ascii字符",
95 | Multibyte: "{0}必须包含多字节字符",
96 | Datauri: "{0}必须包含有效的数据URI",
97 | Latitude: "{0}必须包含有效的纬度坐标",
98 | Longitude: "{0}必须包含有效的经度坐标",
99 | Ssn: "{0}必须是一个有效的社会安全号码(SSN)",
100 | Ipv4: "{0}必须是一个有效的IPv4地址",
101 | Ipv6: "{0}必须是一个有效的IPv6地址",
102 | Ip: "{0}必须是一个有效的IP地址",
103 | Cidr: "{0}必须是一个有效的无类别域间路由(CIDR)",
104 | Cidrv4: "{0}必须是一个包含IPv4地址的有效无类别域间路由(CIDR)",
105 | Cidrv6: "{0}必须是一个包含IPv6地址的有效无类别域间路由(CIDR)",
106 | TcpAddr: "{0}必须是一个有效的TCP地址",
107 | Tcp4Addr: "{0}必须是一个有效的IPv4 TCP地址",
108 | Tcp6Addr: "{0}必须是一个有效的IPv6 TCP地址",
109 | UdpAddr: "{0}必须是一个有效的UDP地址",
110 | Udp4Addr: "{0}必须是一个有效的IPv4 UDP地址",
111 | Udp6Addr: "{0}必须是一个有效的IPv6 UDP地址",
112 | IpAddr: "{0}必须是一个有效的IP地址",
113 | Ip4Addr: "{0}必须是一个有效的IPv4地址",
114 | Ip6Addr: "{0}必须是一个有效的IPv6地址",
115 | UnixAddr: "{0}必须是一个有效的UNIX地址",
116 | Mac: "{0}必须是一个有效的MAC地址",
117 | Unique: "{0}必须是一个唯一值",
118 | Iscolor: "{0}必须是一个有效的颜色",
119 | Oneof: "{0}必须是[{1}]中的一个",
120 | BtcAddr: "{0}必须是一个有效的BTC地址",
121 | BtcAddrBech32: "{0}必须是一个有效的BTC Bech32地址",
122 | EthAddr: "{0}必须是一个有效的ETH地址",
123 |
124 | PluralRuleMap: map[string]lang.PluralRule{
125 | "character": {
126 | One: "字符",
127 | Other: "字符",
128 | },
129 | "item": {
130 | One: "集合",
131 | Other: "集合",
132 | },
133 | },
134 | }
135 | customTranslation := lang.CustomTranslation{
136 | "auth.register.failed_existed": "user register failed, user has been registered before",
137 | "auth.register.failed_system_error": "user register failed, system error",
138 | "auth.register.failed_token_generate_error": "user register failed, token generate failed",
139 |
140 | "auth.login.failed_not_exist": "user login failed, user doesn't exist",
141 | "auth.login.failed_wrong_password": "user login failed, user password incorrect",
142 | "auth.login.failed_token_generate_error": "user login failed, token generate failed",
143 | }
144 | locale.AddLocale("zh", &customTranslation, &validationTranslation)
145 | }
146 |
--------------------------------------------------------------------------------
/resources/views/kernel.go:
--------------------------------------------------------------------------------
1 | package views
2 |
3 | import (
4 | "github.com/totoval/framework/request"
5 | "github.com/totoval/framework/view"
6 | )
7 |
8 | func Initialize(r *request.Engine) {
9 | view.Initialize(r)
10 | }
11 |
--------------------------------------------------------------------------------
/resources/views/user_affiliation.nodes.go:
--------------------------------------------------------------------------------
1 | package views
2 |
3 | import (
4 | "github.com/totoval/framework/view"
5 | )
6 |
7 | func init() {
8 | view.AddView("user_affiliation.nodes", `
9 | {{define "user_affiliation.nodes"}}
10 |
11 |
12 |