├── .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 | ![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/totoval/mindav.svg) 3 | ![GitHub last commit](https://img.shields.io/github/last-commit/totoval/mindav.svg) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/totoval/mindav)](https://goreportcard.com/report/github.com/totoval/mindav) 5 | ![Travis (.org)](https://img.shields.io/travis/totoval/mindav.svg) 6 | ![GitHub top language](https://img.shields.io/github/languages/top/totoval/mindav.svg) 7 | ![GitHub](https://img.shields.io/github/license/totoval/mindav.svg) 8 | ![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/totoval/mindav.svg) 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 | mindav architecture 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 | cyberduck client 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 | 13 | 14 | Nodes 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 25 | 81 | 82 | 83 | {{ end }} 84 | `) 85 | } 86 | -------------------------------------------------------------------------------- /routes/groups/auth.go: -------------------------------------------------------------------------------- 1 | package groups 2 | 3 | import ( 4 | "github.com/totoval/framework/route" 5 | "totoval/app/http/controllers" 6 | ) 7 | 8 | type AuthGroup struct { 9 | LoginController controllers.Login 10 | RegisterController controllers.Register 11 | } 12 | 13 | func (ag *AuthGroup) Group(group route.Grouper) { 14 | group.POST("/login", ag.LoginController.Login) 15 | group.POST("/register", ag.RegisterController.Register) 16 | } 17 | -------------------------------------------------------------------------------- /routes/groups/user.go: -------------------------------------------------------------------------------- 1 | package groups 2 | 3 | import ( 4 | "github.com/totoval/framework/policy" 5 | "github.com/totoval/framework/route" 6 | "totoval/app/http/controllers" 7 | "totoval/app/policies" 8 | ) 9 | 10 | type UserGroup struct { 11 | UserController controllers.User 12 | } 13 | 14 | func (ug *UserGroup) Group(group route.Grouper) { 15 | group.GET("/info/:userId", ug.UserController.Info).Can(policies.NewUserPolicy(), policy.ActionView) 16 | 17 | group.GET("/update", ug.UserController.Update) 18 | group.GET("/delete", ug.UserController.Delete) 19 | group.GET("/delete-transaction", ug.UserController.DeleteTransaction) 20 | group.GET("/logout", ug.UserController.LogOut) 21 | group.GET("/restore", ug.UserController.Restore) 22 | } 23 | -------------------------------------------------------------------------------- /routes/groups/user_affiliation.go: -------------------------------------------------------------------------------- 1 | package groups 2 | 3 | import ( 4 | "github.com/totoval/framework/route" 5 | "totoval/app/http/controllers" 6 | ) 7 | 8 | type UserAffiliationGroup struct { 9 | UserAffiliationController controllers.UserAffiliation 10 | } 11 | 12 | func (uaffg *UserAffiliationGroup) Group(group route.Grouper) { 13 | group.GET("/all", uaffg.UserAffiliationController.RenderAll) 14 | } 15 | -------------------------------------------------------------------------------- /routes/groups/webdav.go: -------------------------------------------------------------------------------- 1 | package groups 2 | 3 | import ( 4 | "fmt" 5 | "github.com/totoval/framework/config" 6 | "github.com/totoval/framework/request" 7 | "github.com/totoval/framework/route" 8 | 9 | "totoval/app/http/controllers" 10 | ) 11 | 12 | type WebDAVGroup struct { 13 | WebDAVController controllers.WebDAV 14 | } 15 | 16 | func (wdg *WebDAVGroup) Group(group route.Grouper) { 17 | webDAVGroup := WebDAVRouterGroup{group} 18 | // webDAVGroup.WebDAVAny(config.GetString("webdav.base_url"), wdg.WebDAVController.Handle) 19 | // webDAVGroup.WebDAVAny(config.GetString("webdav.base_url") + "/", wdg.WebDAVController.Handle) 20 | // 21 | // webDAVGroup.WebDAVAnyPaths(config.GetString("webdav.base_url"), wdg.WebDAVController.Handle) 22 | webDAVGroup.WebDAVAny("", wdg.WebDAVController.Handle) 23 | webDAVGroup.WebDAVAny("/", wdg.WebDAVController.Handle) 24 | 25 | webDAVGroup.WebDAVAnyPaths("", wdg.WebDAVController.Handle) 26 | } 27 | 28 | type WebDAVRouterGroup struct { 29 | route.Grouper 30 | } 31 | 32 | func (wdrg *WebDAVRouterGroup) WebDAVAny(relativePath string, handlers ...request.HandlerFunc) { 33 | wdrg.Any(relativePath, handlers...) 34 | wdrg.Handle("PROPFIND", relativePath, handlers...) 35 | wdrg.Handle("PROPPATCH", relativePath, handlers...) 36 | wdrg.Handle("MKCOL", relativePath, handlers...) 37 | wdrg.Handle("COPY", relativePath, handlers...) 38 | wdrg.Handle("MOVE", relativePath, handlers...) 39 | wdrg.Handle("LOCK", relativePath, handlers...) 40 | wdrg.Handle("UNLOCK", relativePath, handlers...) 41 | } 42 | func (wdrg *WebDAVRouterGroup) WebDAVAnyPaths(basePath string, handlers ...request.HandlerFunc) { 43 | currentPath := "/" 44 | for i := 0; i < config.GetInt("webdav.supported_folder_depth"); i++ { 45 | p := fmt.Sprintf(":path%d", i) 46 | currentPath += p 47 | wdrg.WebDAVAny(basePath+currentPath, handlers...) 48 | currentPath += "/" 49 | wdrg.WebDAVAny(basePath+currentPath, handlers...) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /routes/provider.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/totoval/framework/request" 5 | "github.com/totoval/framework/route" 6 | "totoval/routes/versions" 7 | ) 8 | 9 | func Register(router *request.Engine) { 10 | defer route.Bind() 11 | 12 | versions.NewV1(router) 13 | } 14 | -------------------------------------------------------------------------------- /routes/versions/v1.go: -------------------------------------------------------------------------------- 1 | package versions 2 | 3 | import ( 4 | "github.com/totoval/framework/config" 5 | "github.com/totoval/framework/request" 6 | "github.com/totoval/framework/route" 7 | "totoval/routes/groups" 8 | ) 9 | 10 | func NewV1(engine *request.Engine) { 11 | ver := route.NewVersion(engine, "v1") // here must have a version 12 | 13 | // auth routes 14 | // ver.Auth("", func(grp route.Grouper) { 15 | // grp.AddGroup("/user", &groups.UserGroup{}) 16 | // }) 17 | 18 | // no auth routes 19 | ver.NoAuth("", func(grp route.Grouper) { 20 | grp.AddGroup(config.GetString("webdav.base_url"), &groups.WebDAVGroup{}) 21 | // grp.AddGroup("", &groups.AuthGroup{}) 22 | // grp.AddGroup("/user-affiliation", &groups.UserAffiliationGroup{}) 23 | }) 24 | } 25 | --------------------------------------------------------------------------------