├── .air.toml
├── .gitignore
├── README.md
├── admin
├── .gitignore
├── index.html
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.css
│ ├── App.tsx
│ ├── assets
│ │ └── react.svg
│ ├── config
│ │ ├── api.provider.ts
│ │ └── constants.ts
│ ├── index.css
│ ├── main.tsx
│ ├── resources
│ │ └── posts
│ │ │ ├── components
│ │ │ ├── Create
│ │ │ │ └── index.tsx
│ │ │ ├── Edit
│ │ │ │ └── index.tsx
│ │ │ ├── List
│ │ │ │ └── index.tsx
│ │ │ ├── MobileGrid
│ │ │ │ └── index.tsx
│ │ │ └── Show
│ │ │ │ └── index.tsx
│ │ │ └── resource.ts
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── yarn.lock
├── cmd
└── crudgen
│ └── crudgen.go
├── common
├── env_keys.go
├── helpers.go
└── structs.go
├── crud
├── crud_service.go
├── repository.go
├── router.go
├── service.go
├── structs.go
└── utils.go
├── db
├── database.go
├── migrations.go
├── models
│ └── post.go
└── seed
│ └── posts.go
├── docs
├── docs.go
├── swagger.json
└── swagger.yaml
├── generators
├── create_service.go
├── init_project.go
└── templates
│ ├── controller.tmpl
│ ├── db
│ ├── database.tmpl
│ └── migrations.tmpl
│ ├── env.tmpl
│ ├── gitignore.tmpl
│ ├── main.tmpl
│ ├── model.tmpl
│ ├── repository.tmpl
│ ├── routes.tmpl
│ └── service.tmpl
├── go.mod
├── go.sum
├── main.go
├── pkg
├── helpers
│ └── ensure_dir.go
└── writetemplate
│ └── process_template.go
├── posts
├── controller.go
├── repository.go
├── routes.go
└── service.go
├── requests
├── crud.http
└── posts.http
└── templates
├── db
├── database.tmpl
└── migrations.tmpl
└── main.tmpl
/.air.toml:
--------------------------------------------------------------------------------
1 | root = "."
2 | testdata_dir = "testdata"
3 | tmp_dir = "tmp"
4 |
5 | [build]
6 | args_bin = []
7 | bin = "./tmp/main"
8 | cmd = "go build -o ./tmp/main ."
9 | delay = 1000
10 | exclude_dir = ["assets", "tmp", "vendor", "testdata"]
11 | exclude_file = []
12 | exclude_regex = ["_test.go"]
13 | exclude_unchanged = false
14 | follow_symlink = false
15 | full_bin = ""
16 | include_dir = []
17 | include_ext = ["go", "tpl", "tmpl", "html"]
18 | kill_delay = "0s"
19 | log = "build-errors.log"
20 | send_interrupt = false
21 | stop_on_error = true
22 |
23 | [color]
24 | app = ""
25 | build = "yellow"
26 | main = "magenta"
27 | runner = "green"
28 | watcher = "cyan"
29 |
30 | [log]
31 | time = false
32 |
33 | [misc]
34 | clean_on_exit = false
35 |
36 | [screen]
37 | clear_on_rebuild = false
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | tmp
3 | .env
4 | *.local.db
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Go gin crud starter
2 | This repo could be used as a kind of inherited CRUD api, and it will save a lot of time.
3 |
4 | ## story
5 |
6 | I used to handle crud operations in Nodejs with nest crud package, and it is fully compatible with react admin.
7 | I've never spent any time to make sorting or selecting or filtering functionalities. I was able to create
8 | admin dashboard in no time. When I switched to golang I missed these productive tools so I decided to create it.
9 |
10 |
11 | ## Features
12 | - Full crud features
13 | - sorting, selecting and do complex filters like nested "and" & "or" queries
14 | - add joins and nested joins from uri
15 | - compatible with [ra-admin](https://www.radmin.com/)
16 | - inspired from [@nestjsx/crud](https://github.com/nestjsx/crud)
17 |
18 | ## packages I am using
19 | - Gin
20 | - Gorm
21 | - Postgres
22 | - Air
23 | - Swagger
24 |
25 | Fell free to make PR to add other frameworks or ORMs
26 | ## How to use
27 |
28 | ### Install
29 | `go install github.com/ElegantSoft/go-restful-generator/cmd/crudgen@latest`
30 |
31 | - create new golang project
32 | - go get github.com/ElegantSoft/go-restful-generator@latest
33 | - create db/models folder for models
34 | - create db/db.go with package db for db connection
35 |
36 |
37 | `crudgen service --name service-name --path lib/apps/service-name`
38 |
39 | ### Configuration
40 | - Create `.env` file for configuration.
41 | - Add to it `DB_URL` as a connection string which is in used by [Gorm](https://gorm.io/docs/connecting_to_the_database.html)
42 | - By default `PORT` is configured on 8080, but you can specify in the `.env` file a different `PORT` if you like so.
43 | - If you want to have the documentation also updated then follow the next steps:
44 | - Install `swaggo` with `go install github.com/swaggo/swag/cmd/swag@latest`
45 | - Run in terminal `swag init` which will update the swagger files in the `docs` directory.
46 |
47 | ### See examples as posts then duplicate it
48 |
49 | First you can see how crud api works by running project and go to `/docs/index.html`
50 |
51 | - duplicate posts folder
52 | - change package name
53 | - create your entity in models folder
54 | - replace `model` struct in `repository.go`
55 |
56 | ### requests params
57 | - [Query params](#query-params)
58 | - [select](#select)
59 | - [search](#search)
60 | - [filter conditions](#filter-conditions)
61 | - [filter](#filter)
62 | - [or](#or)
63 | - [sort](#sort)
64 | - [join](#join)
65 | - [limit](#limit)
66 | - [offset](#offset)
67 | - [page](#page)
68 | - [cache](#cache)
69 | - [Frontend usage](#frontend-usage)
70 | - [Customize](#customize)
71 | - [Usage](#usage)
72 |
73 | ## Query params
74 |
75 | By default, we support these param names:
76 |
77 | `fields` - get selected fields in GET result
78 |
79 | `s` - search conditions (`$and`, `$or` with all possible variations)
80 |
81 | `filter` - filter GET result by `AND` type of condition
82 |
83 | `join` - receive joined relational resources in GET result (with all or selected fields)
84 |
85 | `sort` - sort GET result by some `field` in `ASC | DESC` order
86 |
87 | `limit` - limit the amount of received resources
88 |
89 | `page` - receive a portion of limited amount of resources
90 |
91 |
92 | **_Notice:_** You can easily map your own query params names and chose another string delimiters by applying [global options](https://github.com/nestjsx/crud/wiki/Controllers#global-options).
93 |
94 | Here is the description of each of those using default params names:
95 |
96 | ### select
97 |
98 | Selects fields that should be returned in the reponse body.
99 |
100 | _Syntax:_
101 |
102 | > ?fields=**field1**,**field2**,...
103 |
104 | _Example:_
105 |
106 | > ?fields=**email**,**name**
107 |
108 | ### search
109 |
110 | Adds a search condition as a JSON string to you request. You can combine `$and`, `$or` and use any [condition](#filter-conditions) you need. Make sure it's being sent encoded or just use [`RequestQueryBuilder`](#frontend-usage)
111 |
112 | _Syntax:_
113 |
114 | > ?s={"name": "Michael"}
115 |
116 | _Some examples:_
117 |
118 | - Search by field `name` that can be either `null` OR equals `Superman`
119 |
120 | > ?s={"name": {"**\$or**": {"**\$isnull**": true, "**\$eq**": "Superman"}}}
121 |
122 | - Search an entity where `isActive` is `true` AND `createdAt` not equal `2008-10-01T17:04:32`
123 |
124 | > ?s={"**\$and**": [{"isActive": true}, {"createdAt": {"**$ne**": "2008-10-01T17:04:32"}}]}
125 |
126 | ...which is the same as:
127 |
128 | > ?s={"isActive": true, "createdAt": {"**\$ne**": "2008-10-01T17:04:32"}}
129 |
130 | - Search an entity where `isActive` is `false` OR `updatedAt` is not `null`
131 |
132 | > ?s={"**\$or**": [{"isActive": false}, {"updatedAt": {"**$notnull**": true}}]}
133 |
134 | So the amount of combinations is really huge.
135 |
136 | **_Notice:_** if search query param is present, then [filter](#filter) and [or](#or) query params will be ignored.
137 |
138 | ### filter conditions
139 |
140 | - **`$eq`** (`=`, equal)
141 | - **`$ne`** (`!=`, not equal)
142 | - **`$gt`** (`>`, greater than)
143 | - **`$lt`** (`<`, lower that)
144 | - **`$gte`** (`>=`, greater than or equal)
145 | - **`$lte`** (`<=`, lower than or equal)
146 | - **`$cont`** (`LIKE %val%`, contains)
147 |
148 | ### filter
149 |
150 | Adds fields request condition (multiple conditions) to your request.
151 |
152 | _Syntax:_
153 |
154 | > ?filter=**field**||**\$condition**||**value**
155 |
156 | > ?join=**relation**&filter=**relation**.**field**||**\$condition**||**value**
157 |
158 | **_Notice:_** Using nested filter shall join relation first.
159 |
160 | _Examples:_
161 |
162 | > ?filter=**name**||**\$eq**||**batman**
163 |
164 | > ?filter=**isVillain**||**\$eq**||**false**&filter=**city**||**\$eq**||**Arkham** (multiple filters are treated as a combination of `AND` type of conditions)
165 |
166 | > ?filter=**shots**||**\$in**||**12**,**26** (some conditions accept multiple values separated by commas)
167 |
168 | > ?filter=**power**||**\$isnull** (some conditions don't accept value)
169 |
170 | ### or
171 |
172 | Adds `OR` conditions to the request.
173 |
174 | _Syntax:_
175 |
176 | > ?or=**field**||**\$condition**||**value**
177 |
178 | It uses the same [filter conditions](#filter-conditions).
179 |
180 | _Rules and examples:_
181 |
182 | - If there is only **one** `or` present (without `filter`) then it will be interpreted as simple [filter](#filter):
183 |
184 | > ?or=**name**||**\$eq**||**batman**
185 |
186 | - If there are **multiple** `or` present (without `filter`) then it will be interpreted as a compination of `OR` conditions, as follows:
187 | `WHERE {or} OR {or} OR ...`
188 |
189 | > ?or=**name**||**\$eq**||**batman**&or=**name**||**\$eq**||**joker**
190 |
191 | - If there are **one** `or` and **one** `filter` then it will be interpreted as `OR` condition, as follows:
192 | `WHERE {filter} OR {or}`
193 |
194 | > ?filter=**name**||**\$eq**||**batman**&or=**name**||**\$eq**||**joker**
195 |
196 | - If present **both** `or` and `filter` in any amount (**one** or **miltiple** each) then both interpreted as a combitation of `AND` conditions and compared with each other by `OR` condition, as follows:
197 | `WHERE ({filter} AND {filter} AND ...) OR ({or} AND {or} AND ...)`
198 |
199 | > ?filter=**type**||**\$eq**||**hero**&filter=**status**||**\$eq**||**alive**&or=**type**||**\$eq**||**villain**&or=**status**||**\$eq**||**dead**
200 |
201 | ### sort
202 |
203 | Adds sort by field (by multiple fields) and order to query result.
204 |
205 | _Syntax:_
206 |
207 | > ?sort=**field**,**ASC|DESC**
208 |
209 | _Examples:_
210 |
211 | > ?sort=**name**,**ASC**
212 |
213 | > ?sort=**name**,**ASC**&sort=**id**,**DESC**
214 |
215 | ### join
216 |
217 | Receive joined relational objects in GET result (with all or selected fields). You can join as many relations as allowed in your [CrudOptions](https://github.com/nestjsx/crud/wiki/Controllers#join).
218 |
219 | _Syntax:_
220 |
221 | > ?join=**relation**
222 |
223 | > ?join=**relation**||**field1**,**field2**,...
224 |
225 | > ?join=**relation1**||**field11**,**field12**,...&join=**relation1**.**nested**||**field21**,**field22**,...&join=...
226 |
227 | _Examples:_
228 |
229 | > ?join=**profile**
230 |
231 | > ?join=**profile**||**firstName**,**email**
232 |
233 | > ?join=**profile**||**firstName**,**email**&join=**notifications**||**content**&join=**tasks**
234 |
235 | > ?join=**relation1**&join=**relation1**.**nested**&join=**relation1**.**nested**.**deepnested**
236 |
237 | **_Notice:_** primary field/column always persists in relational objects. To use nested relations, the parent level **MUST** be set before the child level like example above.
238 |
239 | ### limit
240 |
241 | Receive `N` amount of entities.
242 |
243 | _Syntax:_
244 |
245 | > ?limit=**number**
246 |
247 | _Example:_
248 |
249 | > ?limit=**10**
250 |
251 | ### offset
252 |
253 | Limit the amount of received resources
254 |
255 | _Syntax:_
256 |
257 | > ?offset=**number**
258 |
259 | _Example:_
260 |
261 | > ?offset=**10**
262 |
263 | ### page
264 |
265 | Receive a portion of limited amount of resources.
266 |
267 | _Syntax:_
268 |
269 | > ?page=**number**
270 |
271 | _Example:_
272 |
273 | > ?page=**2**
274 |
--------------------------------------------------------------------------------
/admin/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/admin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/admin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "admin",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@nestjsx/crud-request": "^5.0.0-alpha.3",
13 | "react": "^18.2.0",
14 | "react-admin": "^4.3.1",
15 | "react-dom": "^18.2.0"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^18.0.17",
19 | "@types/react-dom": "^18.0.6",
20 | "@vitejs/plugin-react": "^2.1.0",
21 | "typescript": "^4.6.4",
22 | "vite": "^3.1.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/admin/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.react:hover {
17 | filter: drop-shadow(0 0 2em #61dafbaa);
18 | }
19 |
20 | @keyframes logo-spin {
21 | from {
22 | transform: rotate(0deg);
23 | }
24 | to {
25 | transform: rotate(360deg);
26 | }
27 | }
28 |
29 | @media (prefers-reduced-motion: no-preference) {
30 | a:nth-of-type(2) .logo {
31 | animation: logo-spin infinite 20s linear;
32 | }
33 | }
34 |
35 | .card {
36 | padding: 2em;
37 | }
38 |
39 | .read-the-docs {
40 | color: #888;
41 | }
42 |
--------------------------------------------------------------------------------
/admin/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { Admin, fetchUtils, Resource } from 'react-admin'
3 | import { apiProvider } from './config/api.provider'
4 | import { API_URL } from './config/constants'
5 | import { postsResource } from './resources/posts/resource'
6 |
7 | const httpClient = (url: string, options: any = {}) => {
8 | if (!options.headers) {
9 | options.headers = new Headers({ Accept: "application/json" })
10 | }
11 | const token = localStorage.getItem("token")
12 | options.headers.set("Authorization", `Bearer ${token}`)
13 | return fetchUtils.fetchJson(url, options)
14 | }
15 |
16 | const dataProvider = apiProvider(API_URL, httpClient)
17 |
18 | function App() {
19 | const [count, setCount] = useState(0)
20 |
21 |
22 | return (
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | export default App
30 |
--------------------------------------------------------------------------------
/admin/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/config/api.provider.ts:
--------------------------------------------------------------------------------
1 | import {
2 | RequestQueryBuilder,
3 | QueryFilterArr,
4 | CondOperator,
5 | } from "@nestjsx/crud-request";
6 | import {
7 | CREATE,
8 | DELETE,
9 | DELETE_MANY,
10 | fetchUtils,
11 | GET_LIST,
12 | GET_MANY,
13 | GET_MANY_REFERENCE,
14 | GET_ONE,
15 | UPDATE,
16 | UPDATE_MANY,
17 | } from "react-admin";
18 |
19 | export const apiProvider = (apiUrl, httpClient = fetchUtils.fetchJson) => {
20 | const composeFilter = (paramsFilter) => {
21 | if (
22 | paramsFilter === "" ||
23 | (typeof paramsFilter.q !== "undefined" && paramsFilter.q === "")
24 | ) {
25 | paramsFilter = {};
26 | }
27 |
28 | const flatFilter = fetchUtils.flattenObject(paramsFilter);
29 | const filter = Object.keys(flatFilter).map((key) => {
30 | const splitKey = key.split("||");
31 | const ops = splitKey[1] ? splitKey[1] : "cont";
32 | let field = splitKey[0];
33 |
34 | if (field.indexOf("_") === 0 && field.indexOf(".") > -1) {
35 | field = field.split(/\.(.+)/)[1];
36 | }
37 | return { field, operator: ops, value: flatFilter[key] };
38 | });
39 | console.log("filter -> ", filter);
40 | return filter;
41 | };
42 |
43 | const convertDataRequestToHTTP = (type, resource, params) => {
44 | let url = "";
45 | const options: any = {};
46 | switch (type) {
47 | case GET_LIST: {
48 | const { page, perPage } = params.pagination;
49 |
50 | let query = RequestQueryBuilder.create({
51 | filter: composeFilter(params.filter) as QueryFilterArr,
52 | })
53 | .setLimit(perPage)
54 | .setPage(page)
55 | .sortBy(params.sort)
56 | .setOffset((page - 1) * perPage)
57 | .query();
58 | const regex = /filter%5B\d%5D/gi;
59 | console.log(query.replace(regex, "filter"));
60 | query = query.replaceAll(regex, "filter");
61 | if (resource === "moderators") {
62 | url = `${apiUrl}/users/moderators?${query}`;
63 | } else {
64 | url = `${apiUrl}/${resource}?${query}`;
65 | }
66 |
67 | break;
68 | }
69 | case GET_ONE: {
70 | if (resource === "moderators") {
71 | url = `${apiUrl}/users/${params.id}`;
72 | } else {
73 | url = `${apiUrl}/${resource}/${params.id}`;
74 | }
75 | break;
76 | }
77 | case GET_MANY: {
78 | const query = RequestQueryBuilder.create()
79 | .setFilter({
80 | field: "id",
81 | operator: CondOperator.IN,
82 | value: `${params.ids}`,
83 | })
84 | .query();
85 |
86 | if (resource === "moderators") {
87 | url = `${apiUrl}/users?${query}`;
88 | } else {
89 | url = `${apiUrl}/${resource}?${query}`;
90 | }
91 | break;
92 | }
93 | case GET_MANY_REFERENCE: {
94 | const { page, perPage } = params.pagination;
95 | const filter = composeFilter(params.filter) as QueryFilterArr;
96 |
97 | filter.push({
98 | field: params.target,
99 | operator: CondOperator.EQUALS,
100 | value: params.id,
101 | });
102 |
103 | const query = RequestQueryBuilder.create({
104 | filter,
105 | })
106 | .sortBy(params.sort)
107 | .setLimit(perPage)
108 | .setOffset((page - 1) * perPage)
109 | .query();
110 |
111 | if (resource === "moderators") {
112 | url = `${apiUrl}/users?${query}`;
113 | } else {
114 | url = `${apiUrl}/${resource}?${query}`;
115 | }
116 | break;
117 | }
118 | case UPDATE: {
119 | if (resource === "moderators") {
120 | url = `${apiUrl}/users/${params.id}`;
121 | } else {
122 | url = `${apiUrl}/${resource}/${params.id}`;
123 | }
124 | options.method = "PATCH";
125 | options.body = JSON.stringify(params.data);
126 | break;
127 | }
128 | case CREATE: {
129 | if (resource === "tickets") {
130 | const formData = new FormData();
131 | for (const param in params.data) {
132 | // when using multiple files
133 | if (param === "files") {
134 | params.data[param].forEach((file) => {
135 | formData.append("files", file.rawFile);
136 | });
137 | continue;
138 | }
139 |
140 | formData.append(param, params.data[param]);
141 | }
142 | url = `${apiUrl}/${resource}`;
143 | options.method = "POST";
144 | options.body = formData;
145 | break;
146 | } else if (resource === "moderators") {
147 | url = `${apiUrl}/users/admin-create`;
148 | } else {
149 | url = `${apiUrl}/${resource}`;
150 | }
151 | options.method = "POST";
152 | options.body = JSON.stringify(params.data);
153 | break;
154 | }
155 | case DELETE: {
156 | if (resource === "moderators") {
157 | url = `${apiUrl}/users/${params.id}`;
158 | } else {
159 | url = `${apiUrl}/${resource}/${params.id}`;
160 | }
161 | options.method = "DELETE";
162 | break;
163 | }
164 | default:
165 | throw new Error(`Unsupported fetch action type ${type}`);
166 | }
167 | return { url, options };
168 | };
169 |
170 | const convertHTTPResponse = (response, type, resource, params) => {
171 | const { headers, json } = response;
172 | switch (type) {
173 | case GET_LIST:
174 | case GET_MANY_REFERENCE:
175 | return {
176 | data: json.data,
177 | total: json.total,
178 | };
179 | case CREATE:
180 | return { data: { ...params.data, id: json.id } };
181 | default:
182 | return { data: json };
183 | }
184 | };
185 |
186 | return (type, resource, params) => {
187 | if (type === UPDATE_MANY) {
188 | return Promise.all(
189 | params.ids.map((id) =>
190 | httpClient(`${apiUrl}/${resource}/${id}`, {
191 | method: "PUT",
192 | body: JSON.stringify(params.data),
193 | })
194 | )
195 | ).then((responses) => ({
196 | data: responses.map((response) => response.json),
197 | }));
198 | }
199 | if (type === DELETE_MANY) {
200 | return Promise.all(
201 | params.ids.map((id) =>
202 | httpClient(`${apiUrl}/${resource}/${id}`, {
203 | method: "DELETE",
204 | })
205 | )
206 | ).then((responses) => ({
207 | data: responses.map((response) => response.json),
208 | }));
209 | }
210 |
211 | const { url, options } = convertDataRequestToHTTP(type, resource, params);
212 | return httpClient(url, options).then((response) =>
213 | convertHTTPResponse(response, type, resource, params)
214 | );
215 | };
216 | };
217 |
--------------------------------------------------------------------------------
/admin/src/config/constants.ts:
--------------------------------------------------------------------------------
1 | export const API_URL = "http://localhost:8080"
2 |
3 |
--------------------------------------------------------------------------------
/admin/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | a {
19 | font-weight: 500;
20 | color: #646cff;
21 | text-decoration: inherit;
22 | }
23 | a:hover {
24 | color: #535bf2;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid transparent;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #1a1a1a;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
59 | @media (prefers-color-scheme: light) {
60 | :root {
61 | color: #213547;
62 | background-color: #ffffff;
63 | }
64 | a:hover {
65 | color: #747bff;
66 | }
67 | button {
68 | background-color: #f9f9f9;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/admin/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 |
5 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
6 |
7 |
8 |
9 | )
10 |
--------------------------------------------------------------------------------
/admin/src/resources/posts/components/Create/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Create, SimpleForm, TextInput } from "react-admin"
3 |
4 | export const CategoryCreate = (props) => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/admin/src/resources/posts/components/Edit/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BooleanInput,
3 | Edit,
4 | RadioButtonGroupInput,
5 | SimpleForm,
6 | TextInput,
7 | } from "react-admin"
8 | import * as React from "react"
9 |
10 | export const CategoryEdit = (props) => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/admin/src/resources/posts/components/List/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from "react"
2 | import {
3 | Datagrid,
4 | List,
5 | TextField,
6 | Pagination,
7 | Filter,
8 | TextInput,
9 | SingleFieldList,
10 | NumberInput,
11 | ChipField,
12 | BooleanField,
13 | ArrayField,
14 | DatagridProps,
15 | } from "react-admin"
16 | import { MobileGrid } from "../MobileGrid"
17 |
18 | const PostPagination = () => (
19 |
20 | )
21 |
22 |
23 | // const BlacklistFilter = (props) => (
24 | //
25 | //
26 | //
27 | //
28 | // )
29 |
30 | const filters = [
31 | ,
32 | ,
33 | ]
34 |
35 | export const CategoryList = () => {
36 |
37 | return (
38 |
}
40 | filters={filters}
41 | hasCreate
42 | perPage={25}
43 | >
44 |
45 |
46 |
47 |
48 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/admin/src/resources/posts/components/MobileGrid/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Card, CardHeader, CardContent } from "@material-ui/core"
3 | import { makeStyles } from "@material-ui/core/styles"
4 | import {
5 | DateField,
6 | EditButton,
7 | NumberField,
8 | TextField,
9 | BooleanField,
10 | useTranslate,
11 | RecordMap,
12 | Identifier,
13 | Record,
14 | } from "react-admin"
15 |
16 | const useListStyles = makeStyles((theme) => ({
17 | card: {
18 | height: "100%",
19 | display: "flex",
20 | flexDirection: "column",
21 | margin: "0.5rem 0",
22 | },
23 | cardTitleContent: {
24 | display: "flex",
25 | flexDirection: "row",
26 | alignItems: "center",
27 | justifyContent: "space-between",
28 | },
29 | cardContent: theme.typography.body1,
30 | cardContentRow: {
31 | display: "flex",
32 | flexDirection: "row",
33 | alignItems: "center",
34 | margin: "0.5rem 0",
35 | },
36 | }))
37 |
38 | interface MobileGridProps {
39 | ids?: Identifier[]
40 | data?: RecordMap
41 | basePath?: string
42 | }
43 |
44 | export const MobileGrid = (props: MobileGridProps) => {
45 | const { ids, data, basePath } = props
46 | const translate = useTranslate()
47 | const classes = useListStyles()
48 |
49 | if (!ids || !data || !basePath) {
50 | return null
51 | }
52 |
53 | return (
54 |
55 | {ids.map((id) => (
56 |
57 |
60 |
61 | المعرف:
62 |
63 |
68 |
69 | }
70 | />
71 |
72 |
73 | {"الإسم: "}
74 |
75 |
76 |
77 | ))}
78 |
79 | )
80 | }
81 |
--------------------------------------------------------------------------------
/admin/src/resources/posts/components/Show/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import {
3 | Show,
4 | SimpleShowLayout,
5 | TextField,
6 | EditButton,
7 | TopToolbar,
8 | } from "react-admin"
9 |
10 |
11 | export const CategoryShow = () =>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/admin/src/resources/posts/resource.ts:
--------------------------------------------------------------------------------
1 | import { CategoryList } from "./components/List"
2 | import { CategoryCreate } from "./components/Create"
3 | import { CategoryEdit } from "./components/Edit"
4 | import { CategoryShow } from "./components/Show"
5 |
6 | export const postsResource = {
7 | list: CategoryList,
8 | show: CategoryShow,
9 | edit: CategoryEdit,
10 | create: CategoryCreate,
11 | }
12 |
--------------------------------------------------------------------------------
/admin/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/admin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "noImplicitAny": false
19 | },
20 | "include": ["src"],
21 | "references": [{ "path": "./tsconfig.node.json" }]
22 | }
23 |
--------------------------------------------------------------------------------
/admin/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/admin/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/cmd/crudgen/crudgen.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/ElegantSoft/go-restful-generator/common"
6 | "github.com/ElegantSoft/go-restful-generator/generators"
7 | "github.com/spf13/cobra"
8 | "log"
9 | "os"
10 | )
11 |
12 | func main() {
13 | //promptGetServiceName := promptui.Prompt{
14 | // Label: "service name",
15 | //}
16 | moduleName := common.GetModuleName()
17 |
18 | rootCmd := &cobra.Command{
19 | Use: "crudgen",
20 | Short: "crudgen gen cli tool",
21 | Run: func(cmd *cobra.Command, args []string) {
22 | log.Println("Please use crudgen init or crudgen service")
23 | },
24 | }
25 |
26 | initCmd := &cobra.Command{
27 | Use: "init",
28 | Short: "init new project structure",
29 | Run: func(cmd *cobra.Command, args []string) {
30 | generators.InitNewProject(moduleName)
31 | },
32 | }
33 |
34 | var serviceName string
35 | var servicePath string
36 |
37 | generateServiceCmd := &cobra.Command{
38 | Use: "service",
39 | Short: "generate new service",
40 | Run: func(cmd *cobra.Command, args []string) {
41 | if serviceName == "" {
42 | log.Fatal("you must set service name ex: --name posts")
43 | return
44 | }
45 | generators.GenerateService(moduleName, serviceName, servicePath)
46 | },
47 | }
48 |
49 | rootCmd.PersistentFlags().StringVar(&serviceName, "name", "", "service name ex: posts")
50 | rootCmd.PersistentFlags().StringVar(&servicePath, "path", "", "service path default lib/service-name ex: services/service-name")
51 |
52 | rootCmd.AddCommand(initCmd, generateServiceCmd)
53 |
54 | if err := rootCmd.Execute(); err != nil {
55 | fmt.Println(err)
56 | os.Exit(1)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/common/env_keys.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | const (
4 | KJwtSecret string = "JWT_SECRET"
5 | KUserHeader string = "user_id"
6 | KAWSSession string = "aws_session"
7 | )
8 |
--------------------------------------------------------------------------------
/common/helpers.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "golang.org/x/mod/modfile"
5 | "os"
6 | "reflect"
7 | )
8 |
9 | func Unique(intSlice []uint) []uint {
10 | keys := make(map[uint]bool)
11 | var list []uint
12 | for _, entry := range intSlice {
13 | if _, value := keys[entry]; !value {
14 | keys[entry] = true
15 | list = append(list, entry)
16 | }
17 | }
18 | return list
19 | }
20 |
21 | func GetIdFromCtx(id interface{}) uint {
22 | idFloat, _ := id.(float64)
23 | return uint(idFloat)
24 | }
25 |
26 | func Contains(val interface{}, slice interface{}) bool {
27 | found := false
28 | for _, v := range slice.([]uint) {
29 | if v == val {
30 | found = true
31 | }
32 | }
33 | return found
34 | }
35 | func StringsContains(val string, slice []string) bool {
36 | found := false
37 | for _, v := range slice {
38 | if v == val {
39 | found = true
40 | }
41 | }
42 | return found
43 | }
44 |
45 | func HashIntersection(a interface{}, b interface{}) []interface{} {
46 | set := make([]interface{}, 0)
47 | hash := make(map[interface{}]bool)
48 | av := reflect.ValueOf(a)
49 | bv := reflect.ValueOf(b)
50 |
51 | for i := 0; i < av.Len(); i++ {
52 | el := av.Index(i).Interface()
53 | hash[el] = true
54 | }
55 |
56 | for i := 0; i < bv.Len(); i++ {
57 | el := bv.Index(i).Interface()
58 | if _, found := hash[el]; found {
59 | set = append(set, el)
60 | }
61 | }
62 |
63 | return set
64 | }
65 |
66 | func removeDuplicateAdjacent(checkText string) string {
67 | newText := ""
68 | for i := range []rune(checkText) {
69 | if i+1 < len([]rune(checkText)) {
70 | if []rune(checkText)[i] != []rune(checkText)[i+1] {
71 | newText += string([]rune(checkText)[i])
72 | }
73 |
74 | } else {
75 | newText += string([]rune(checkText)[i])
76 | }
77 |
78 | }
79 | return newText
80 | }
81 |
82 | func GetModuleName() string {
83 | goModBytes, err := os.ReadFile("go.mod")
84 | if err != nil {
85 | panic(err)
86 | }
87 |
88 | modName := modfile.ModulePath(goModBytes)
89 |
90 | return modName
91 | }
92 |
--------------------------------------------------------------------------------
/common/structs.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | type ById struct {
4 | ID string `uri:"id" binding:"required"`
5 | }
6 |
--------------------------------------------------------------------------------
/crud/crud_service.go:
--------------------------------------------------------------------------------
1 | package crud
2 |
--------------------------------------------------------------------------------
/crud/repository.go:
--------------------------------------------------------------------------------
1 | package crud
2 |
3 | import (
4 | "gorm.io/gorm"
5 | )
6 |
7 | type Repo[T any] interface {
8 | FindOne(cond *T, dest *T) error
9 | Update(cond *T, updatedColumns *T) error
10 | Delete(cond *T) error
11 | Create(data *T) error
12 | getTx() *gorm.DB
13 | }
14 |
15 | type Repository[T any] struct {
16 | DB *gorm.DB
17 | Model T
18 | }
19 |
20 | func (r *Repository[T]) FindOne(cond *T, dest *T) error {
21 | return r.DB.Where(cond).First(dest).Error
22 | }
23 |
24 | func (r *Repository[T]) Update(cond *T, updatedColumns *T) error {
25 | return r.DB.Model(r.Model).Select("*").Where(cond).Updates(updatedColumns).Error
26 | }
27 |
28 | func (r *Repository[T]) Delete(cond *T) error {
29 | if err := r.DB.Model(r.Model).Delete(cond); err != nil {
30 | return err.Error
31 | }
32 | return nil
33 | }
34 |
35 | func (r *Repository[T]) Create(data *T) error {
36 | return r.DB.Create(data).Error
37 | }
38 |
39 | func (r *Repository[T]) getTx() *gorm.DB {
40 | return r.DB.Model(r.Model)
41 | }
42 |
43 | func NewRepository[T any](db *gorm.DB, model T) Repo[T] {
44 | return &Repository[T]{
45 | DB: db,
46 | Model: model,
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/crud/router.go:
--------------------------------------------------------------------------------
1 | package crud
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "net/http"
7 |
8 | "github.com/ElegantSoft/go-restful-generator/db"
9 | "github.com/ElegantSoft/go-restful-generator/db/models"
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | func RegisterRoutes(routerGroup *gin.RouterGroup) {
14 | repo := NewRepository[models.Post](db.DB, models.Post{})
15 | s := NewService[models.Post](repo)
16 |
17 | routerGroup.GET("", func(ctx *gin.Context) {
18 | var api GetAllRequest
19 | if err := ctx.ShouldBindQuery(&api); err != nil {
20 | ctx.JSON(400, gin.H{"message": err.Error()})
21 | return
22 | }
23 |
24 | var result []models.Post
25 | var totalRows int64
26 | err := s.Find(api, &result, &totalRows)
27 | if err != nil {
28 | ctx.JSON(400, gin.H{"message": err.Error()})
29 | return
30 | }
31 |
32 | var data interface{}
33 | if api.Page > 0 {
34 | data = map[string]interface{}{
35 | "data": result,
36 | "total": totalRows,
37 | "totalPages": int(math.Ceil(float64(totalRows) / float64(api.Limit))),
38 | }
39 | } else {
40 | data = result
41 | }
42 | ctx.JSON(200, data)
43 | })
44 |
45 | routerGroup.GET(":id", func(ctx *gin.Context) {
46 | var api GetAllRequest
47 | var item ById
48 | if err := ctx.ShouldBindQuery(&api); err != nil {
49 | ctx.JSON(400, gin.H{"message": err.Error()})
50 | return
51 | }
52 | if err := ctx.ShouldBindUri(&item); err != nil {
53 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
54 | return
55 | }
56 |
57 | api.Filter = append(api.Filter, fmt.Sprintf("id||$eq||%s", item.ID))
58 |
59 | var result models.Post
60 |
61 | err := s.FindOne(api, &result)
62 | if err != nil {
63 | ctx.JSON(400, gin.H{"message": err.Error()})
64 | return
65 | }
66 | ctx.JSON(200, result)
67 | })
68 | }
69 |
--------------------------------------------------------------------------------
/crud/service.go:
--------------------------------------------------------------------------------
1 | package crud
2 |
3 | import (
4 | "encoding/json"
5 | "gorm.io/gorm"
6 | "strings"
7 | )
8 |
9 | type Service[T any] struct {
10 | Repo Repo[T]
11 | Qtb *QueryToDBConverter
12 | }
13 |
14 | func (svc *Service[T]) FindTrx(api GetAllRequest) (error, *gorm.DB) {
15 | var s map[string]interface{}
16 | if len(api.S) > 0 {
17 | err := json.Unmarshal([]byte(api.S), &s)
18 | if err != nil {
19 | return err, nil
20 | }
21 | }
22 |
23 | tx := svc.Repo.getTx()
24 | if len(api.Fields) > 0 {
25 | fields := strings.Split(api.Fields, ",")
26 | tx.Select(fields)
27 | }
28 | if len(api.Join) > 0 {
29 | svc.Qtb.relationsMapper(api.Join, tx)
30 | }
31 |
32 | if len(api.Filter) > 0 {
33 | svc.Qtb.filterMapper(api.Filter, tx)
34 | }
35 |
36 | if len(api.Sort) > 0 {
37 | svc.Qtb.sortMapper(api.Sort, tx)
38 | }
39 |
40 | err := svc.Qtb.searchMapper(s, tx)
41 | if err != nil {
42 | return err, nil
43 | }
44 |
45 | tx.Limit(api.Limit)
46 |
47 | return nil, tx
48 | }
49 |
50 | func (svc *Service[T]) Find(api GetAllRequest, result interface{}, totalRows *int64) error {
51 | err, tx := svc.FindTrx(api)
52 | tx.Count(totalRows)
53 | if api.Page > 0 {
54 | tx.Offset((api.Page - 1) * api.Limit)
55 | }
56 | if err != nil {
57 | return err
58 | }
59 | return tx.Find(result).Error
60 | }
61 |
62 | func (svc *Service[T]) FindOne(api GetAllRequest, result interface{}) error {
63 | var s map[string]interface{}
64 | if len(api.S) > 0 {
65 | err := json.Unmarshal([]byte(api.S), &s)
66 | if err != nil {
67 | return err
68 | }
69 | }
70 |
71 | tx := svc.Repo.getTx()
72 |
73 | if len(api.Fields) > 0 {
74 | fields := strings.Split(api.Fields, ",")
75 | tx.Select(fields)
76 | }
77 | if len(api.Join) > 0 {
78 | svc.Qtb.relationsMapper(api.Join, tx)
79 | }
80 |
81 | if len(api.Filter) > 0 {
82 | svc.Qtb.filterMapper(api.Filter, tx)
83 | }
84 |
85 | if len(api.Sort) > 0 {
86 | svc.Qtb.sortMapper(api.Sort, tx)
87 | }
88 |
89 | err := svc.Qtb.searchMapper(s, tx)
90 | if err != nil {
91 | return err
92 | }
93 | return tx.First(result).Error
94 | }
95 |
96 | func (svc *Service[T]) Create(data *T) error {
97 | return svc.Repo.Create(data)
98 | }
99 |
100 | func (svc *Service[T]) Delete(cond *T) error {
101 | return svc.Repo.Delete(cond)
102 | }
103 |
104 | func (svc *Service[T]) Update(cond *T, updatedColumns *T) error {
105 | return svc.Repo.Update(cond, updatedColumns)
106 | }
107 |
108 | func NewService[T any](repo Repo[T]) *Service[T] {
109 | return &Service[T]{
110 | Repo: repo,
111 | Qtb: &QueryToDBConverter{},
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/crud/structs.go:
--------------------------------------------------------------------------------
1 | package crud
2 |
3 | const (
4 | AND = "$and"
5 | OR = "$or"
6 | SEPARATOR = "||"
7 | SortSeparator = ","
8 | )
9 |
10 | type GetAllRequest struct {
11 | Page int `json:"page" form:"page"`
12 | Limit int `json:"limit" form:"limit"`
13 | Join string `json:"join" form:"join"`
14 | S string `json:"s" form:"s"`
15 | Fields string `json:"fields" form:"fields"`
16 | Filter []string `json:"filter" form:"filter"`
17 | Sort []string `json:"sort" form:"sort"`
18 | }
19 |
20 | var filterConditions = map[string]string{
21 | "eq": "=",
22 | "ne": "!=",
23 | "gt": ">",
24 | "lt": "<",
25 | "gte": ">=",
26 | "lte": "<=",
27 | "$in": "in",
28 | "cont": "ILIKE",
29 | "isnull": "IS NULL",
30 | "notnull": "IS NOT NULL",
31 | }
32 |
33 | type ById struct {
34 | ID string `uri:"id" binding:"required"`
35 | }
36 |
--------------------------------------------------------------------------------
/crud/utils.go:
--------------------------------------------------------------------------------
1 | package crud
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "golang.org/x/text/cases"
7 | "golang.org/x/text/language"
8 | "strings"
9 |
10 | "gorm.io/gorm"
11 | )
12 |
13 | const (
14 | ContainOperator = "cont"
15 | NotNullOperator = "notnull"
16 | IsNullOperator = "isnull"
17 | InOperator = "$in"
18 | )
19 |
20 | var AndValueNotSlice = errors.New("the value of $and or $or not array")
21 |
22 | type QueryToDBConverter struct {
23 | }
24 |
25 | func (q *QueryToDBConverter) searchMapper(s map[string]interface{}, tx *gorm.DB) error {
26 | for k := range s {
27 | if k == AND {
28 | vals, ok := s[k].([]interface{})
29 | if !ok {
30 | return AndValueNotSlice
31 | }
32 | for _, field := range vals {
33 | keyAndVal, ok := field.(map[string]interface{})
34 | if ok {
35 | for whereField, whereVal := range keyAndVal {
36 | whereValMap, ok := whereVal.(map[string]interface{})
37 | if ok {
38 | for operatorKey, value := range whereValMap {
39 | operator, ok := filterConditions[operatorKey]
40 | if ok {
41 | if operatorKey == NotNullOperator || operatorKey == IsNullOperator {
42 | tx.Where(fmt.Sprintf("%s %s", whereField, operator))
43 | } else if operatorKey == InOperator {
44 | valSlice := strings.Split(value.(string), ",")
45 | tx.Where(fmt.Sprintf("%s IN ?", whereField), valSlice)
46 | } else {
47 |
48 | if operatorKey == ContainOperator {
49 | value = fmt.Sprintf("%%%s%%", value)
50 | }
51 | tx.Where(fmt.Sprintf("%s %s ?", whereField, operator), value)
52 | }
53 | }
54 | }
55 |
56 | } else {
57 |
58 | tx.Where(whereField, whereVal)
59 | }
60 | }
61 | }
62 | }
63 | } else if k == OR {
64 | vals, ok := s[k].([]interface{})
65 | if !ok {
66 | return AndValueNotSlice
67 | }
68 | for i, field := range vals {
69 | keyAndVal, ok := field.(map[string]interface{})
70 | if ok {
71 | for whereField, whereVal := range keyAndVal {
72 | whereValMap, ok := whereVal.(map[string]interface{})
73 | if ok {
74 | for operatorKey, value := range whereValMap {
75 | operator, ok := filterConditions[operatorKey]
76 | if ok {
77 | if operatorKey == NotNullOperator || operatorKey == IsNullOperator {
78 | if i == 0 {
79 | tx.Where(fmt.Sprintf("%s %s", whereField, operator))
80 | } else {
81 | tx.Or(fmt.Sprintf("%s %s", whereField, operator))
82 | }
83 | } else if operatorKey == InOperator {
84 | if i == 0 {
85 | valSlice := strings.Split(value.(string), ",")
86 | tx.Where(fmt.Sprintf("%s IN ?", whereField), valSlice)
87 | } else {
88 | valSlice := strings.Split(value.(string), ",")
89 | tx.Or(fmt.Sprintf("%s IN ?", whereField), valSlice)
90 | }
91 | } else {
92 | if operatorKey == ContainOperator {
93 | value = fmt.Sprintf("%%%s%%", value)
94 | }
95 | if i == 0 {
96 | tx.Where(fmt.Sprintf("%s %s ?", whereField, operator), value)
97 | } else {
98 | tx.Or(fmt.Sprintf("%s %s ?", whereField, operator), value)
99 | }
100 | }
101 | }
102 | }
103 |
104 | } else {
105 | if i == 0 {
106 | tx.Where(whereField, whereVal)
107 | } else {
108 | tx.Or(whereField, whereVal)
109 | }
110 | }
111 | }
112 | }
113 | }
114 |
115 | }
116 |
117 | }
118 | return nil
119 | }
120 |
121 | func (q *QueryToDBConverter) relationsMapper(joinString string, tx *gorm.DB) {
122 | relations := strings.Split(joinString, ",")
123 | for _, relation := range relations {
124 | nestedRelationsSlice := strings.Split(relation, ".")
125 | titledSlice := make([]string, len(nestedRelationsSlice))
126 | for i, relation := range nestedRelationsSlice {
127 | titledSlice[i] = cases.Title(language.English, cases.NoLower).String(relation)
128 | }
129 | nestedRelation := strings.Join(titledSlice, ".")
130 | if len(nestedRelation) > 0 {
131 | tx.Preload(nestedRelation)
132 | }
133 | }
134 | }
135 |
136 | func (q *QueryToDBConverter) filterMapper(filters []string, tx *gorm.DB) {
137 | for _, filter := range filters {
138 | filterParams := strings.Split(filter, SEPARATOR)
139 | if len(filterParams) >= 2 {
140 | operator, ok := filterConditions[filterParams[1]]
141 | if ok {
142 | if filterParams[1] == NotNullOperator || filterParams[1] == IsNullOperator {
143 | tx.Where(fmt.Sprintf("%s %s", filterParams[0], operator))
144 | } else {
145 | if len(filterParams) == 3 {
146 |
147 | if filterParams[1] == ContainOperator {
148 | tx.Where(fmt.Sprintf("%s %s ?", filterParams[0], operator), fmt.Sprintf("%%%s%%", filterParams[2]))
149 | } else if filterParams[1] == InOperator {
150 | valSlice := strings.Split(filterParams[2], ",")
151 | tx.Where(fmt.Sprintf("%s IN ?", filterParams[0]), valSlice)
152 | } else {
153 | tx.Where(fmt.Sprintf("%s %s ?", filterParams[0], operator), filterParams[2])
154 |
155 | }
156 | }
157 | }
158 | }
159 | }
160 | }
161 | }
162 |
163 | func (q *QueryToDBConverter) sortMapper(sorts []string, tx *gorm.DB) {
164 | for _, sort := range sorts {
165 | sortParams := strings.Split(sort, SortSeparator)
166 | if len(sortParams) == 2 {
167 | tx.Order(fmt.Sprintf("%s %s", sortParams[0], strings.ToLower(sortParams[1])))
168 | } else {
169 | tx.Order(fmt.Sprintf("%s desc", sortParams[0]))
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/db/database.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "gorm.io/driver/postgres"
5 | "gorm.io/driver/sqlite"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | "log"
9 | "os"
10 | "time"
11 | )
12 |
13 | var DB *gorm.DB
14 |
15 | func Open(dsn string) error {
16 | newLogger := logger.New(
17 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
18 | logger.Config{
19 | SlowThreshold: time.Second, // Slow SQL threshold
20 | LogLevel: logger.Info, // Log level
21 | Colorful: true, // Disable color
22 |
23 | },
24 | )
25 | var err error
26 | DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
27 | Logger: newLogger,
28 | SkipDefaultTransaction: true,
29 | PrepareStmt: false,
30 | })
31 | if err != nil {
32 | return err
33 | }
34 | return nil
35 | }
36 |
37 | func OpenTestDB() error {
38 | newLogger := logger.New(
39 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
40 | logger.Config{
41 | SlowThreshold: time.Second, // Slow SQL threshold
42 | LogLevel: logger.Info, // Log level
43 | Colorful: true, // Disable color
44 |
45 | },
46 | )
47 | var err error
48 | DB, err = gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
49 | Logger: newLogger,
50 | SkipDefaultTransaction: true,
51 | PrepareStmt: true,
52 | })
53 | if err != nil {
54 | return err
55 | }
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/db/migrations.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | func AddUUIDExtension() {
4 | DB.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`)
5 | }
6 |
--------------------------------------------------------------------------------
/db/models/post.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/google/uuid"
5 | "time"
6 | )
7 |
8 | type Post struct {
9 | ID uuid.UUID `json:"id,omitempty" gorm:"type:uuid; default:uuid_generate_v4()"`
10 | Title string `json:"title,omitempty"`
11 | Description string `json:"description,omitempty"`
12 | CategoryID uuid.NullUUID `json:"category_id,omitempty"`
13 | Category *Category `json:"category,omitempty"`
14 | Price uint32 `json:"price,omitempty"`
15 | UpdatedAt time.Time `json:"updated_at,omitempty"`
16 | CreatedAt time.Time `json:"created_at,omitempty"`
17 | }
18 |
19 | type Category struct {
20 | ID uuid.UUID `json:"id,omitempty" gorm:"type:uuid; default:uuid_generate_v4()"`
21 | Name string `json:"name,omitempty"`
22 | Posts *[]Post `json:"posts,omitempty"`
23 | UpdatedAt time.Time `json:"updated_at,omitempty"`
24 | CreatedAt time.Time `json:"created_at,omitempty"`
25 | }
26 |
--------------------------------------------------------------------------------
/db/seed/posts.go:
--------------------------------------------------------------------------------
1 | package seed
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/ElegantSoft/go-restful-generator/db"
7 | "github.com/ElegantSoft/go-restful-generator/db/models"
8 | "github.com/bxcodec/faker/v3"
9 | "github.com/google/uuid"
10 | )
11 |
12 | func SeedPosts() {
13 | for i := 0; i < 20; i++ {
14 | cat := models.Category{
15 | Name: faker.Word(),
16 | }
17 | log.Printf("will create cat %v %v", i, cat)
18 | err := db.DB.Create(&cat).Error
19 | if err != nil {
20 | log.Printf("error while seed category %+v", err)
21 | }
22 | for l := 0; l < 50; l++ {
23 | post := models.Post{
24 | Title: faker.Word(),
25 | Description: faker.Paragraph(),
26 | CategoryID: uuid.NullUUID{
27 | UUID: cat.ID,
28 | Valid: true,
29 | },
30 | Price: uint32(l + 1*i + 1),
31 | }
32 | err := db.DB.Create(&post).Error
33 | if err != nil {
34 | log.Printf("error while seed post %+v", err)
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/docs/docs.go:
--------------------------------------------------------------------------------
1 | // Package docs GENERATED BY SWAG; DO NOT EDIT
2 | // This file was generated by swaggo/swag
3 | package docs
4 |
5 | import "github.com/swaggo/swag"
6 |
7 | const docTemplate = `{
8 | "schemes": {{ marshal .Schemes }},
9 | "swagger": "2.0",
10 | "info": {
11 | "description": "{{escape .Description}}",
12 | "title": "{{.Title}}",
13 | "contact": {
14 | "name": "API Support",
15 | "url": "http://www.swagger.io/support",
16 | "email": "support@swagger.io"
17 | },
18 | "license": {
19 | "name": "Apache 2.0",
20 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
21 | },
22 | "version": "{{.Version}}"
23 | },
24 | "host": "{{.Host}}",
25 | "basePath": "{{.BasePath}}",
26 | "paths": {
27 | "/posts": {
28 | "get": {
29 | "tags": [
30 | "posts"
31 | ],
32 | "parameters": [
33 | {
34 | "type": "string",
35 | "description": "{'$and': [ {'title': { '$cont':'cul' } } ]}",
36 | "name": "s",
37 | "in": "query"
38 | },
39 | {
40 | "type": "string",
41 | "description": "fields to select eg: name,age",
42 | "name": "fields",
43 | "in": "query"
44 | },
45 | {
46 | "type": "integer",
47 | "description": "page of pagination",
48 | "name": "page",
49 | "in": "query"
50 | },
51 | {
52 | "type": "integer",
53 | "description": "limit of pagination",
54 | "name": "limit",
55 | "in": "query"
56 | },
57 | {
58 | "type": "string",
59 | "description": "join relations eg: category, parent",
60 | "name": "join",
61 | "in": "query"
62 | },
63 | {
64 | "type": "array",
65 | "items": {
66 | "type": "string"
67 | },
68 | "description": "filters eg: name||$eq||ad price||$gte||200",
69 | "name": "filter",
70 | "in": "query"
71 | },
72 | {
73 | "type": "array",
74 | "items": {
75 | "type": "string"
76 | },
77 | "description": "filters eg: created_at,desc title,asc",
78 | "name": "sort",
79 | "in": "query"
80 | }
81 | ],
82 | "responses": {
83 | "200": {
84 | "description": "OK",
85 | "schema": {
86 | "type": "array",
87 | "items": {
88 | "$ref": "#/definitions/posts.model"
89 | }
90 | }
91 | }
92 | }
93 | },
94 | "post": {
95 | "tags": [
96 | "posts"
97 | ],
98 | "parameters": [
99 | {
100 | "description": "item to create",
101 | "name": "{object}",
102 | "in": "body",
103 | "required": true,
104 | "schema": {
105 | "$ref": "#/definitions/posts.model"
106 | }
107 | }
108 | ],
109 | "responses": {
110 | "201": {
111 | "description": "Created",
112 | "schema": {
113 | "$ref": "#/definitions/posts.model"
114 | }
115 | }
116 | }
117 | }
118 | },
119 | "/posts/{id}": {
120 | "get": {
121 | "tags": [
122 | "posts"
123 | ],
124 | "parameters": [
125 | {
126 | "type": "string",
127 | "description": "uuid of item",
128 | "name": "id",
129 | "in": "path",
130 | "required": true
131 | }
132 | ],
133 | "responses": {
134 | "200": {
135 | "description": "OK",
136 | "schema": {
137 | "$ref": "#/definitions/posts.model"
138 | }
139 | }
140 | }
141 | },
142 | "put": {
143 | "tags": [
144 | "posts"
145 | ],
146 | "parameters": [
147 | {
148 | "type": "string",
149 | "description": "uuid of item",
150 | "name": "id",
151 | "in": "path",
152 | "required": true
153 | },
154 | {
155 | "description": "update body",
156 | "name": "item",
157 | "in": "body",
158 | "required": true,
159 | "schema": {
160 | "$ref": "#/definitions/posts.model"
161 | }
162 | }
163 | ],
164 | "responses": {
165 | "200": {
166 | "description": "ok",
167 | "schema": {
168 | "type": "string"
169 | }
170 | }
171 | }
172 | },
173 | "delete": {
174 | "tags": [
175 | "posts"
176 | ],
177 | "parameters": [
178 | {
179 | "type": "string",
180 | "description": "uuid of item",
181 | "name": "id",
182 | "in": "path",
183 | "required": true
184 | }
185 | ],
186 | "responses": {
187 | "200": {
188 | "description": "ok",
189 | "schema": {
190 | "type": "string"
191 | }
192 | }
193 | }
194 | }
195 | }
196 | },
197 | "definitions": {
198 | "models.Category": {
199 | "type": "object",
200 | "properties": {
201 | "created_at": {
202 | "type": "string"
203 | },
204 | "id": {
205 | "type": "string"
206 | },
207 | "name": {
208 | "type": "string"
209 | },
210 | "posts": {
211 | "type": "array",
212 | "items": {
213 | "$ref": "#/definitions/models.Post"
214 | }
215 | },
216 | "updated_at": {
217 | "type": "string"
218 | }
219 | }
220 | },
221 | "models.Post": {
222 | "type": "object",
223 | "properties": {
224 | "category": {
225 | "$ref": "#/definitions/models.Category"
226 | },
227 | "category_id": {
228 | "type": "string"
229 | },
230 | "created_at": {
231 | "type": "string"
232 | },
233 | "description": {
234 | "type": "string"
235 | },
236 | "id": {
237 | "type": "string"
238 | },
239 | "price": {
240 | "type": "integer"
241 | },
242 | "title": {
243 | "type": "string"
244 | },
245 | "updated_at": {
246 | "type": "string"
247 | }
248 | }
249 | },
250 | "posts.model": {
251 | "type": "object",
252 | "properties": {
253 | "category": {
254 | "$ref": "#/definitions/models.Category"
255 | },
256 | "category_id": {
257 | "type": "string"
258 | },
259 | "created_at": {
260 | "type": "string"
261 | },
262 | "description": {
263 | "type": "string"
264 | },
265 | "id": {
266 | "type": "string"
267 | },
268 | "price": {
269 | "type": "integer"
270 | },
271 | "title": {
272 | "type": "string"
273 | },
274 | "updated_at": {
275 | "type": "string"
276 | }
277 | }
278 | }
279 | }
280 | }`
281 |
282 | // SwaggerInfo holds exported Swagger Info so clients can modify it
283 | var SwaggerInfo = &swag.Spec{
284 | Version: "",
285 | Host: "",
286 | BasePath: "",
287 | Schemes: []string{},
288 | Title: "",
289 | Description: "",
290 | InfoInstanceName: "swagger",
291 | SwaggerTemplate: docTemplate,
292 | }
293 |
294 | func init() {
295 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
296 | }
297 |
--------------------------------------------------------------------------------
/docs/swagger.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "contact": {
5 | "name": "API Support",
6 | "url": "http://www.swagger.io/support",
7 | "email": "support@swagger.io"
8 | },
9 | "license": {
10 | "name": "Apache 2.0",
11 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
12 | }
13 | },
14 | "paths": {
15 | "/posts": {
16 | "get": {
17 | "tags": [
18 | "posts"
19 | ],
20 | "parameters": [
21 | {
22 | "type": "string",
23 | "description": "{'$and': [ {'title': { '$cont':'cul' } } ]}",
24 | "name": "s",
25 | "in": "query"
26 | },
27 | {
28 | "type": "string",
29 | "description": "fields to select eg: name,age",
30 | "name": "fields",
31 | "in": "query"
32 | },
33 | {
34 | "type": "integer",
35 | "description": "page of pagination",
36 | "name": "page",
37 | "in": "query"
38 | },
39 | {
40 | "type": "integer",
41 | "description": "limit of pagination",
42 | "name": "limit",
43 | "in": "query"
44 | },
45 | {
46 | "type": "string",
47 | "description": "join relations eg: category, parent",
48 | "name": "join",
49 | "in": "query"
50 | },
51 | {
52 | "type": "array",
53 | "items": {
54 | "type": "string"
55 | },
56 | "description": "filters eg: name||$eq||ad price||$gte||200",
57 | "name": "filter",
58 | "in": "query"
59 | },
60 | {
61 | "type": "array",
62 | "items": {
63 | "type": "string"
64 | },
65 | "description": "filters eg: created_at,desc title,asc",
66 | "name": "sort",
67 | "in": "query"
68 | }
69 | ],
70 | "responses": {
71 | "200": {
72 | "description": "OK",
73 | "schema": {
74 | "type": "array",
75 | "items": {
76 | "$ref": "#/definitions/posts.model"
77 | }
78 | }
79 | }
80 | }
81 | },
82 | "post": {
83 | "tags": [
84 | "posts"
85 | ],
86 | "parameters": [
87 | {
88 | "description": "item to create",
89 | "name": "{object}",
90 | "in": "body",
91 | "required": true,
92 | "schema": {
93 | "$ref": "#/definitions/posts.model"
94 | }
95 | }
96 | ],
97 | "responses": {
98 | "201": {
99 | "description": "Created",
100 | "schema": {
101 | "$ref": "#/definitions/posts.model"
102 | }
103 | }
104 | }
105 | }
106 | },
107 | "/posts/{id}": {
108 | "get": {
109 | "tags": [
110 | "posts"
111 | ],
112 | "parameters": [
113 | {
114 | "type": "string",
115 | "description": "uuid of item",
116 | "name": "id",
117 | "in": "path",
118 | "required": true
119 | }
120 | ],
121 | "responses": {
122 | "200": {
123 | "description": "OK",
124 | "schema": {
125 | "$ref": "#/definitions/posts.model"
126 | }
127 | }
128 | }
129 | },
130 | "put": {
131 | "tags": [
132 | "posts"
133 | ],
134 | "parameters": [
135 | {
136 | "type": "string",
137 | "description": "uuid of item",
138 | "name": "id",
139 | "in": "path",
140 | "required": true
141 | },
142 | {
143 | "description": "update body",
144 | "name": "item",
145 | "in": "body",
146 | "required": true,
147 | "schema": {
148 | "$ref": "#/definitions/posts.model"
149 | }
150 | }
151 | ],
152 | "responses": {
153 | "200": {
154 | "description": "ok",
155 | "schema": {
156 | "type": "string"
157 | }
158 | }
159 | }
160 | },
161 | "delete": {
162 | "tags": [
163 | "posts"
164 | ],
165 | "parameters": [
166 | {
167 | "type": "string",
168 | "description": "uuid of item",
169 | "name": "id",
170 | "in": "path",
171 | "required": true
172 | }
173 | ],
174 | "responses": {
175 | "200": {
176 | "description": "ok",
177 | "schema": {
178 | "type": "string"
179 | }
180 | }
181 | }
182 | }
183 | }
184 | },
185 | "definitions": {
186 | "models.Category": {
187 | "type": "object",
188 | "properties": {
189 | "created_at": {
190 | "type": "string"
191 | },
192 | "id": {
193 | "type": "string"
194 | },
195 | "name": {
196 | "type": "string"
197 | },
198 | "posts": {
199 | "type": "array",
200 | "items": {
201 | "$ref": "#/definitions/models.Post"
202 | }
203 | },
204 | "updated_at": {
205 | "type": "string"
206 | }
207 | }
208 | },
209 | "models.Post": {
210 | "type": "object",
211 | "properties": {
212 | "category": {
213 | "$ref": "#/definitions/models.Category"
214 | },
215 | "category_id": {
216 | "type": "string"
217 | },
218 | "created_at": {
219 | "type": "string"
220 | },
221 | "description": {
222 | "type": "string"
223 | },
224 | "id": {
225 | "type": "string"
226 | },
227 | "price": {
228 | "type": "integer"
229 | },
230 | "title": {
231 | "type": "string"
232 | },
233 | "updated_at": {
234 | "type": "string"
235 | }
236 | }
237 | },
238 | "posts.model": {
239 | "type": "object",
240 | "properties": {
241 | "category": {
242 | "$ref": "#/definitions/models.Category"
243 | },
244 | "category_id": {
245 | "type": "string"
246 | },
247 | "created_at": {
248 | "type": "string"
249 | },
250 | "description": {
251 | "type": "string"
252 | },
253 | "id": {
254 | "type": "string"
255 | },
256 | "price": {
257 | "type": "integer"
258 | },
259 | "title": {
260 | "type": "string"
261 | },
262 | "updated_at": {
263 | "type": "string"
264 | }
265 | }
266 | }
267 | }
268 | }
--------------------------------------------------------------------------------
/docs/swagger.yaml:
--------------------------------------------------------------------------------
1 | definitions:
2 | models.Category:
3 | properties:
4 | created_at:
5 | type: string
6 | id:
7 | type: string
8 | name:
9 | type: string
10 | posts:
11 | items:
12 | $ref: '#/definitions/models.Post'
13 | type: array
14 | updated_at:
15 | type: string
16 | type: object
17 | models.Post:
18 | properties:
19 | category:
20 | $ref: '#/definitions/models.Category'
21 | category_id:
22 | type: string
23 | created_at:
24 | type: string
25 | description:
26 | type: string
27 | id:
28 | type: string
29 | price:
30 | type: integer
31 | title:
32 | type: string
33 | updated_at:
34 | type: string
35 | type: object
36 | posts.model:
37 | properties:
38 | category:
39 | $ref: '#/definitions/models.Category'
40 | category_id:
41 | type: string
42 | created_at:
43 | type: string
44 | description:
45 | type: string
46 | id:
47 | type: string
48 | price:
49 | type: integer
50 | title:
51 | type: string
52 | updated_at:
53 | type: string
54 | type: object
55 | info:
56 | contact:
57 | email: support@swagger.io
58 | name: API Support
59 | url: http://www.swagger.io/support
60 | license:
61 | name: Apache 2.0
62 | url: http://www.apache.org/licenses/LICENSE-2.0.html
63 | paths:
64 | /posts:
65 | get:
66 | parameters:
67 | - description: '{''$and'': [ {''title'': { ''$cont'':''cul'' } } ]}'
68 | in: query
69 | name: s
70 | type: string
71 | - description: 'fields to select eg: name,age'
72 | in: query
73 | name: fields
74 | type: string
75 | - description: page of pagination
76 | in: query
77 | name: page
78 | type: integer
79 | - description: limit of pagination
80 | in: query
81 | name: limit
82 | type: integer
83 | - description: 'join relations eg: category, parent'
84 | in: query
85 | name: join
86 | type: string
87 | - description: 'filters eg: name||$eq||ad price||$gte||200'
88 | in: query
89 | items:
90 | type: string
91 | name: filter
92 | type: array
93 | - description: 'filters eg: created_at,desc title,asc'
94 | in: query
95 | items:
96 | type: string
97 | name: sort
98 | type: array
99 | responses:
100 | "200":
101 | description: OK
102 | schema:
103 | items:
104 | $ref: '#/definitions/posts.model'
105 | type: array
106 | tags:
107 | - posts
108 | post:
109 | parameters:
110 | - description: item to create
111 | in: body
112 | name: '{object}'
113 | required: true
114 | schema:
115 | $ref: '#/definitions/posts.model'
116 | responses:
117 | "201":
118 | description: Created
119 | schema:
120 | $ref: '#/definitions/posts.model'
121 | tags:
122 | - posts
123 | /posts/{id}:
124 | delete:
125 | parameters:
126 | - description: uuid of item
127 | in: path
128 | name: id
129 | required: true
130 | type: string
131 | responses:
132 | "200":
133 | description: ok
134 | schema:
135 | type: string
136 | tags:
137 | - posts
138 | get:
139 | parameters:
140 | - description: uuid of item
141 | in: path
142 | name: id
143 | required: true
144 | type: string
145 | responses:
146 | "200":
147 | description: OK
148 | schema:
149 | $ref: '#/definitions/posts.model'
150 | tags:
151 | - posts
152 | put:
153 | parameters:
154 | - description: uuid of item
155 | in: path
156 | name: id
157 | required: true
158 | type: string
159 | - description: update body
160 | in: body
161 | name: item
162 | required: true
163 | schema:
164 | $ref: '#/definitions/posts.model'
165 | responses:
166 | "200":
167 | description: ok
168 | schema:
169 | type: string
170 | tags:
171 | - posts
172 | swagger: "2.0"
173 |
--------------------------------------------------------------------------------
/generators/create_service.go:
--------------------------------------------------------------------------------
1 | package generators
2 |
3 | import (
4 | _ "embed"
5 | "github.com/ElegantSoft/go-restful-generator/pkg/writetemplate"
6 | "github.com/iancoleman/strcase"
7 | "log"
8 | "os"
9 | "path/filepath"
10 | )
11 |
12 | //go:embed templates/routes.tmpl
13 | var routerTemplate string
14 |
15 | //go:embed templates/controller.tmpl
16 | var controllerTemplate string
17 |
18 | //go:embed templates/service.tmpl
19 | var serviceTemplate string
20 |
21 | //go:embed templates/repository.tmpl
22 | var repositoryTemplate string
23 |
24 | //go:embed templates/model.tmpl
25 | var modelTemplate string
26 |
27 | func GenerateService(packageName string, serviceName string, servicePath string) {
28 |
29 | type Data struct {
30 | PackageName string
31 | ServiceName string
32 | }
33 | data := Data{PackageName: packageName, ServiceName: serviceName}
34 | if servicePath == "" {
35 | servicePath = "lib/" + strcase.ToKebab(serviceName)
36 | }
37 |
38 | err := os.MkdirAll(servicePath, os.ModePerm)
39 | if err != nil {
40 | log.Fatal(err)
41 | return
42 | }
43 |
44 | writetemplate.ProcessTemplate(routerTemplate, "router.tmpl", filepath.Join(servicePath, "router.go"), data)
45 | writetemplate.ProcessTemplate(controllerTemplate, "controller.tmpl", filepath.Join(servicePath, "controller.go"), data)
46 | writetemplate.ProcessTemplate(serviceTemplate, "service.tmpl", filepath.Join(servicePath, "service.go"), data)
47 | writetemplate.ProcessTemplate(repositoryTemplate, "repository.tmpl", filepath.Join(servicePath, "repository.go"), data)
48 | writetemplate.ProcessTemplate(modelTemplate, "model.tmpl", filepath.Join("db/models", strcase.ToSnake(serviceName)+".go"), data)
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/generators/init_project.go:
--------------------------------------------------------------------------------
1 | package generators
2 |
3 | import (
4 | _ "embed"
5 | "github.com/ElegantSoft/go-restful-generator/pkg/writetemplate"
6 | "os"
7 | "path/filepath"
8 | )
9 |
10 | //go:embed templates/main.tmpl
11 | var mainTemplate string
12 |
13 | //go:embed templates/db/database.tmpl
14 | var databaseTemplate string
15 |
16 | //go:embed templates/db/migrations.tmpl
17 | var migrationsTemplate string
18 |
19 | //go:embed templates/env.tmpl
20 | var envTemplate string
21 |
22 | //go:embed templates/gitignore.tmpl
23 | var gitignoreTemplate string
24 |
25 | func InitNewProject(packageName string) {
26 |
27 | type Data struct {
28 | PackageName string
29 | }
30 | data := Data{PackageName: packageName}
31 |
32 | writetemplate.ProcessTemplate(mainTemplate, "main.tmpl", filepath.Join("main.go"), data)
33 | err := os.Mkdir("db/models", os.ModePerm)
34 | if err != nil {
35 | panic(err)
36 | }
37 | err = os.Mkdir("lib", os.ModePerm)
38 | if err != nil {
39 | panic(err)
40 | }
41 | writetemplate.ProcessTemplate(databaseTemplate, "database.tmpl", filepath.Join("db/database.go"), data)
42 | writetemplate.ProcessTemplate(migrationsTemplate, "migrations.tmpl", filepath.Join("db/migrations.go"), data)
43 | writetemplate.ProcessTemplate(envTemplate, "env.tmpl", filepath.Join(".env"), data)
44 | writetemplate.ProcessTemplate(gitignoreTemplate, "gitignore.tmpl", filepath.Join(".gitignore"), data)
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/generators/templates/controller.tmpl:
--------------------------------------------------------------------------------
1 | package {{.ServiceName | camelcase | lower}}
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "net/http"
7 |
8 | "{{.PackageName}}/common"
9 | "github.com/ElegantSoft/go-restful-generator/crud"
10 | "github.com/gin-gonic/gin"
11 | "github.com/google/uuid"
12 | )
13 |
14 | type Controller struct {
15 | service *Service
16 | }
17 |
18 | // @Success 200 {array} model
19 | // @Tags {{.ServiceName}}
20 | // @param s query string false "{'and': [ {'title': { 'cont':'cul' } } ]}"
21 | // @param fields query string false "fields to select eg: name,age"
22 | // @param page query int false "page of pagination"
23 | // @param limit query int false "limit of pagination"
24 | // @param join query string false "join relations eg: category, parent"
25 | // @param filter query []string false "filters eg: name||eq||ad price||gte||200"
26 | // @param sort query []string false "filters eg: created_at,desc title,asc"
27 | // @Router /api/v1/{{.ServiceName | kebabcase}} [get]
28 | func (c *Controller) findAll(ctx *gin.Context) {
29 | var api crud.GetAllRequest
30 | if api.Limit == 0 {
31 | api.Limit = 20
32 | }
33 | if err := ctx.ShouldBindQuery(&api); err != nil {
34 | ctx.JSON(400, gin.H{"message": err.Error()})
35 | return
36 | }
37 |
38 | var result []model
39 | var totalRows int64
40 | err := c.service.Find(api, &result, &totalRows)
41 | if err != nil {
42 | ctx.JSON(400, gin.H{"message": err.Error()})
43 | return
44 | }
45 |
46 | var data interface{}
47 | if api.Page > 0 {
48 | data = map[string]interface{}{
49 | "data": result,
50 | "total": totalRows,
51 | "totalPages": int(math.Ceil(float64(totalRows) / float64(api.Limit))),
52 | }
53 | } else {
54 | data = result
55 | }
56 | ctx.JSON(200, data)
57 | }
58 |
59 | // @Success 200 {object} model
60 | // @Tags {{.ServiceName}}
61 | // @param id path string true "uuid of item"
62 | // @Router /api/v1/{{.ServiceName | kebabcase}}/{id} [get]
63 | func (c *Controller) findOne(ctx *gin.Context) {
64 | var api crud.GetAllRequest
65 | var item common.ById
66 | if err := ctx.ShouldBindQuery(&api); err != nil {
67 | ctx.JSON(400, gin.H{"message": err.Error()})
68 | return
69 | }
70 | if err := ctx.ShouldBindUri(&item); err != nil {
71 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
72 | return
73 | }
74 |
75 | api.Filter = append(api.Filter, fmt.Sprintf("id||eq||%s", item.ID))
76 |
77 | var result model
78 |
79 | err := c.service.FindOne(api, &result)
80 | if err != nil {
81 | ctx.JSON(400, gin.H{"message": err.Error()})
82 | return
83 | }
84 | ctx.JSON(200, result)
85 | }
86 |
87 | // @Success 201 {object} model
88 | // @Tags {{.ServiceName}}
89 | // @param {object} body model true "item to create"
90 | // @Router /api/v1/{{.ServiceName | kebabcase}} [post]
91 | func (c *Controller) create(ctx *gin.Context) {
92 | var item model
93 | if err := ctx.ShouldBind(&item); err != nil {
94 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
95 | return
96 | }
97 | err := c.service.Create(&item)
98 | if err != nil {
99 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
100 | return
101 | }
102 | ctx.JSON(http.StatusCreated, gin.H{"data": item})
103 | }
104 |
105 | // @Success 200 {string} string "ok"
106 | // @Tags {{.ServiceName}}
107 | // @param id path string true "uuid of item"
108 | // @Router /api/v1/{{.ServiceName | kebabcase}}/{id} [delete]
109 | func (c *Controller) delete(ctx *gin.Context) {
110 | var item common.ById
111 | if err := ctx.ShouldBindUri(&item); err != nil {
112 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
113 | return
114 | }
115 |
116 | id, err := uuid.ParseBytes([]byte(item.ID))
117 | if err != nil {
118 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
119 | return
120 | }
121 | err = c.service.Delete(&model{ID: id})
122 | if err != nil {
123 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
124 | return
125 | }
126 | ctx.JSON(http.StatusOK, gin.H{"message": "deleted"})
127 | }
128 |
129 | // @Success 200 {string} string "ok"
130 | // @Tags {{.ServiceName}}
131 | // @param id path string true "uuid of item"
132 | // @param item body model true "update body"
133 | // @Router /api/v1/{{.ServiceName | kebabcase}}/{id} [patch]
134 | func (c *Controller) update(ctx *gin.Context) {
135 | var item model
136 | var byId common.ById
137 | if err := ctx.ShouldBind(&item); err != nil {
138 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
139 | return
140 | }
141 | if err := ctx.ShouldBindUri(&byId); err != nil {
142 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
143 | return
144 | }
145 | id, err := uuid.Parse(byId.ID)
146 | if err != nil {
147 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
148 | return
149 | }
150 | if err := ctx.ShouldBindUri(&byId); err != nil {
151 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
152 | return
153 | }
154 | err = c.service.Update(&model{ID: id}, &item)
155 | if err != nil {
156 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
157 | return
158 | }
159 | ctx.JSON(http.StatusOK, item)
160 | }
161 |
162 | func NewController(service *Service) *Controller {
163 | return &Controller{
164 | service: service,
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/generators/templates/db/database.tmpl:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "gorm.io/driver/sqlite"
5 | "gorm.io/driver/postgres"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | "log"
9 | "os"
10 | "time"
11 | )
12 |
13 | var DB *gorm.DB
14 |
15 | func Open(dsn string) error {
16 | newLogger := logger.New(
17 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
18 | logger.Config{
19 | SlowThreshold: time.Second, // Slow SQL threshold
20 | LogLevel: logger.Info, // Log level
21 | Colorful: true, // Disable color
22 |
23 | },
24 | )
25 | var err error
26 | DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
27 | Logger: newLogger,
28 | SkipDefaultTransaction: true,
29 | PrepareStmt: false,
30 | })
31 | if err != nil {
32 | return err
33 | }
34 | return nil
35 | }
36 |
37 | func OpenTestDB() error {
38 | newLogger := logger.New(
39 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
40 | logger.Config{
41 | SlowThreshold: time.Second, // Slow SQL threshold
42 | LogLevel: logger.Info, // Log level
43 | Colorful: true, // Disable color
44 |
45 | },
46 | )
47 | var err error
48 | DB, err = gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
49 | Logger: newLogger,
50 | SkipDefaultTransaction: true,
51 | PrepareStmt: true,
52 | })
53 | if err != nil {
54 | return err
55 | }
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/generators/templates/db/migrations.tmpl:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | func AddUUIDExtension() {
4 | DB.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`)
5 | }
6 |
--------------------------------------------------------------------------------
/generators/templates/env.tmpl:
--------------------------------------------------------------------------------
1 | DB_URL="host=localhost user=root password=password dbname=db port=5432 sslmode=disable"
2 |
--------------------------------------------------------------------------------
/generators/templates/gitignore.tmpl:
--------------------------------------------------------------------------------
1 | .idea
2 | tmp
3 | .env
4 | *.local.db
5 |
--------------------------------------------------------------------------------
/generators/templates/main.tmpl:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "{{.PackageName}}/db"
7 |
8 | "github.com/gin-contrib/cors"
9 | "github.com/gin-gonic/gin"
10 | "github.com/joho/godotenv"
11 | swaggerFiles "github.com/swaggo/files"
12 | ginSwagger "github.com/swaggo/gin-swagger"
13 | _ "{{.PackageName}}/docs"
14 | )
15 |
16 | // @contact.name API Support
17 | // @contact.url http://www.swagger.io/support
18 | // @contact.email support@swagger.io
19 |
20 | // @license.name Apache 2.0
21 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html
22 | func main() {
23 | if err := godotenv.Load(); err != nil {
24 | log.Fatal("Error loading .env file")
25 | }
26 | server := gin.New()
27 | server.Use(gin.Recovery())
28 |
29 | config := cors.DefaultConfig()
30 | // config.AllowOrigins = []string{"http://localhost:5173"}
31 | config.AllowAllOrigins = true
32 | config.AddAllowHeaders("Authorization")
33 |
34 | server.Use(cors.New(config))
35 |
36 | if os.Getenv("GIN_MODE") == "debug" {
37 | server.Use(gin.Logger())
38 | }
39 | gin.SetMode(os.Getenv("GIN_MODE"))
40 |
41 | if err := db.Open(os.Getenv("DB_URL")); err != nil {
42 | log.Fatal(err)
43 | }
44 | log.Println("server started")
45 |
46 | server.GET("", func(ctx *gin.Context) {
47 | ctx.JSON(200, gin.H{"message": "ok 5"})
48 | })
49 |
50 | // migrations
51 | // TODO: run this migration if u need uuid support for postgres
52 | // db.AddUUIDExtension()
53 |
54 | // if err := db.DB.AutoMigrate(
55 | // TODO: add models here to auto migrate
56 | // ); err != nil {
57 | // log.Fatal(err)
58 | // }
59 |
60 | server.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
61 |
62 |
63 | err := server.Run()
64 | if err != nil {
65 | log.Printf("error while starting server %+v", err)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/generators/templates/model.tmpl:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "github.com/google/uuid"
5 | "time"
6 | )
7 |
8 |
9 | type {{.ServiceName | camelcase | title}} struct {
10 | ID uuid.UUID `json:"id,omitempty" gorm:"type:uuid; default:uuid_generate_v4()"`
11 | UpdatedAt time.Time `json:"updated_at,omitempty"`
12 | CreatedAt time.Time `json:"created_at,omitempty"`
13 | }
14 |
--------------------------------------------------------------------------------
/generators/templates/repository.tmpl:
--------------------------------------------------------------------------------
1 | package {{.ServiceName | camelcase | lower}}
2 |
3 | import (
4 | "github.com/ElegantSoft/go-restful-generator/crud"
5 | "{{.PackageName}}/db"
6 | "{{.PackageName}}/db/models"
7 | )
8 |
9 | type model = models.{{.ServiceName | camelcase | title}}
10 |
11 | type Repository struct {
12 | crud.Repository[model]
13 | }
14 |
15 | func InitRepository() *Repository {
16 | return &Repository{
17 | Repository: crud.Repository[model]{
18 | DB: db.DB,
19 | Model: &model{},
20 | },
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/generators/templates/routes.tmpl:
--------------------------------------------------------------------------------
1 | package {{.ServiceName | camelcase | lower}}
2 |
3 | import "github.com/gin-gonic/gin"
4 |
5 | func RegisterRoutes(routerGroup *gin.RouterGroup) {
6 | service := InitService()
7 | controller := NewController(service)
8 |
9 | routerGroup.GET("", controller.findAll)
10 | routerGroup.GET(":id", controller.findOne)
11 | routerGroup.POST("", controller.create)
12 | routerGroup.DELETE(":id", controller.delete)
13 | routerGroup.PATCH(":id", controller.update)
14 | }
15 |
--------------------------------------------------------------------------------
/generators/templates/service.tmpl:
--------------------------------------------------------------------------------
1 | package {{.ServiceName | camelcase | lower}}
2 |
3 | import (
4 | "github.com/ElegantSoft/go-restful-generator/crud"
5 | )
6 |
7 | type Service struct {
8 | crud.Service[model]
9 | repo *Repository
10 | }
11 |
12 | func NewService(repository *Repository) *Service {
13 | return &Service{
14 | Service: *crud.NewService[model](repository),
15 | repo: repository,
16 | }
17 | }
18 |
19 | func InitService() *Service {
20 | return &Service{
21 | repo: InitRepository(),
22 | Service: *crud.NewService[model](InitRepository()),
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/ElegantSoft/go-restful-generator
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/Masterminds/sprig v2.22.0+incompatible
7 | github.com/bxcodec/faker/v3 v3.8.0
8 | github.com/gin-contrib/cors v1.4.0
9 | github.com/gin-gonic/gin v1.8.1
10 | github.com/go-playground/validator/v10 v10.11.0
11 | github.com/google/uuid v1.3.0
12 | github.com/iancoleman/strcase v0.2.0
13 | github.com/joho/godotenv v1.4.0
14 | github.com/spf13/cobra v1.5.0
15 | github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
16 | github.com/swaggo/gin-swagger v1.5.3
17 | github.com/swaggo/swag v1.8.5
18 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
19 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
20 | gorm.io/driver/postgres v1.3.9
21 | gorm.io/driver/sqlite v1.3.6
22 | gorm.io/gorm v1.23.8
23 | )
24 |
25 | require (
26 | github.com/KyleBanks/depth v1.2.1 // indirect
27 | github.com/Masterminds/goutils v1.1.1 // indirect
28 | github.com/Masterminds/semver v1.5.0 // indirect
29 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
30 | github.com/gin-contrib/sse v0.1.0 // indirect
31 | github.com/go-openapi/jsonpointer v0.19.5 // indirect
32 | github.com/go-openapi/jsonreference v0.20.0 // indirect
33 | github.com/go-openapi/spec v0.20.7 // indirect
34 | github.com/go-openapi/swag v0.22.3 // indirect
35 | github.com/go-playground/locales v0.14.0 // indirect
36 | github.com/go-playground/universal-translator v0.18.0 // indirect
37 | github.com/goccy/go-json v0.9.11 // indirect
38 | github.com/google/go-cmp v0.5.8 // indirect
39 | github.com/huandu/xstrings v1.3.2 // indirect
40 | github.com/imdario/mergo v0.3.13 // indirect
41 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
42 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
43 | github.com/jackc/pgconn v1.13.0 // indirect
44 | github.com/jackc/pgio v1.0.0 // indirect
45 | github.com/jackc/pgpassfile v1.0.0 // indirect
46 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect
47 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
48 | github.com/jackc/pgtype v1.12.0 // indirect
49 | github.com/jackc/pgx/v4 v4.17.2 // indirect
50 | github.com/jinzhu/inflection v1.0.0 // indirect
51 | github.com/jinzhu/now v1.1.5 // indirect
52 | github.com/josharian/intern v1.0.0 // indirect
53 | github.com/json-iterator/go v1.1.12 // indirect
54 | github.com/leodido/go-urn v1.2.1 // indirect
55 | github.com/mailru/easyjson v0.7.7 // indirect
56 | github.com/manifoldco/promptui v0.9.0 // indirect
57 | github.com/mattn/go-isatty v0.0.16 // indirect
58 | github.com/mattn/go-sqlite3 v1.14.15 // indirect
59 | github.com/mitchellh/copystructure v1.2.0 // indirect
60 | github.com/mitchellh/reflectwalk v1.0.2 // indirect
61 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
62 | github.com/modern-go/reflect2 v1.0.2 // indirect
63 | github.com/pelletier/go-toml/v2 v2.0.5 // indirect
64 | github.com/rakyll/statik v0.1.7 // indirect
65 | github.com/spf13/pflag v1.0.5 // indirect
66 | github.com/ugorji/go/codec v1.2.7 // indirect
67 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
68 | golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
69 | golang.org/x/text v0.3.7 // indirect
70 | golang.org/x/tools v0.1.12 // indirect
71 | google.golang.org/protobuf v1.28.1 // indirect
72 | gopkg.in/yaml.v2 v2.4.0 // indirect
73 | gopkg.in/yaml.v3 v3.0.1 // indirect
74 | )
75 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
3 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
4 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
5 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
6 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
7 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
8 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
9 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
10 | github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
11 | github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
12 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
13 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
14 | github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
15 | github.com/bxcodec/faker/v3 v3.8.0 h1:F59Qqnsh0BOtZRC+c4cXoB/VNYDMS3R5mlSpxIap1oU=
16 | github.com/bxcodec/faker/v3 v3.8.0/go.mod h1:gF31YgnMSMKgkvl+fyEo1xuSMbEuieyqfeslGYFjneM=
17 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
18 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
19 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
20 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
21 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
22 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
23 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
24 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
25 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
26 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
27 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
28 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
29 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
30 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
31 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
32 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
33 | github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
34 | github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
35 | github.com/gin-contrib/gzip v0.0.5 h1:mhnVU32YnnBh2LPH2iqRqsA/eR7SAqRaD388jL2s/j0=
36 | github.com/gin-contrib/gzip v0.0.5/go.mod h1:OPIK6HR0Um2vNmBUTlayD7qle4yVVRZT0PyhdUigrKk=
37 | github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
38 | github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
39 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
40 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
41 | github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
42 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
43 | github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
44 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
45 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
46 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
47 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
48 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
49 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
50 | github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
51 | github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
52 | github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
53 | github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
54 | github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
55 | github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
56 | github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI=
57 | github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
58 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
59 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
60 | github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
61 | github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
62 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
63 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
64 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
65 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
66 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
67 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
68 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
69 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
70 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
71 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
72 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
73 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
74 | github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
75 | github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
76 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
77 | github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
78 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
79 | github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
80 | github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
81 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
82 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
83 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
84 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
85 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
86 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
87 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
88 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
89 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
90 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
91 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
92 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
93 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
94 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
95 | github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
96 | github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
97 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
98 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
99 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
100 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
101 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
102 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
103 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
104 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
105 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
106 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
107 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
108 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
109 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
110 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
111 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
112 | github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=
113 | github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
114 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
115 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
116 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
117 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
118 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
119 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
120 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
121 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
122 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
123 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
124 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
125 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
126 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
127 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
128 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
129 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
130 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
131 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
132 | github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=
133 | github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
134 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
135 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
136 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
137 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
138 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
139 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
140 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
141 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
142 | github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=
143 | github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
144 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
145 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
146 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
147 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
148 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
149 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
150 | github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=
151 | github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
152 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
153 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
154 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
155 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
156 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
157 | github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
158 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
159 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
160 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
161 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
162 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
163 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
164 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
165 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
166 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
167 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
168 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
169 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
170 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
171 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
172 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
173 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
174 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
175 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
176 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
177 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
178 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
179 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
180 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
181 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
182 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
183 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
184 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
185 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
186 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
187 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
188 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
189 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
190 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
191 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
192 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
193 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
194 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
195 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
196 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
197 | github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
198 | github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
199 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
200 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
201 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
202 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
203 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
204 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
205 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
206 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
207 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
208 | github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
209 | github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
210 | github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
211 | github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
212 | github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
213 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
214 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
215 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
216 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
217 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
218 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
219 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
220 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
221 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
222 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
223 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
224 | github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
225 | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
226 | github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
227 | github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
228 | github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
229 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
230 | github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
231 | github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
232 | github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
233 | github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
234 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
235 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
236 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
237 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
238 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
239 | github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
240 | github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
241 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
242 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
243 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
244 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
245 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
246 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
247 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
248 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
249 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
250 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
251 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
252 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
253 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
254 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
255 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
256 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
257 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
258 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
259 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
260 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
261 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
262 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
263 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
264 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
265 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
266 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
267 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
268 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
269 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
270 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
271 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
272 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
273 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
274 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
275 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
276 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
277 | github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc=
278 | github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
279 | github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
280 | github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
281 | github.com/swaggo/gin-swagger v1.5.0 h1:hlLbxPj6qvbtX2wpbsZuOIlcnPRCUDGccA0zMKVNpME=
282 | github.com/swaggo/gin-swagger v1.5.0/go.mod h1:3mKpZClKx7mnUGsiwJeEkNhnr1VHMkMaTAXIoFYUXrA=
283 | github.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q=
284 | github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI=
285 | github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
286 | github.com/swaggo/swag v1.8.3 h1:3pZSSCQ//gAH88lfmxM3Cd1+JCsxV8Md6f36b9hrZ5s=
287 | github.com/swaggo/swag v1.8.3/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
288 | github.com/swaggo/swag v1.8.5 h1:7NgtfXsXE+jrcOwRyiftGKW7Ppydj7tZiVenuRf1fE4=
289 | github.com/swaggo/swag v1.8.5/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
290 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
291 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
292 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
293 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
294 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
295 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
296 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
297 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
298 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
299 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
300 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
301 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
302 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
303 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
304 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
305 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
306 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
307 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
308 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
309 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
310 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
311 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
312 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
313 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
314 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
315 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
316 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
317 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
318 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
319 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
320 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
321 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
322 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
323 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
324 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
325 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
326 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
327 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
328 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
329 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
330 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
331 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
332 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
333 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
334 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
335 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
336 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
337 | golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
338 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
339 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
340 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
341 | golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
342 | golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
343 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
344 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
345 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
346 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
347 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
348 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
349 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
350 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
351 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
352 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
353 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
354 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
355 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
356 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
357 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
358 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
359 | golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
360 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
361 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
362 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
363 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
364 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
365 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
366 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
367 | golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
368 | golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
369 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
370 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
371 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
372 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
373 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
374 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
375 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
376 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
377 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
378 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
379 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
380 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
381 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
382 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
383 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
384 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
385 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
386 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
387 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
388 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
389 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
390 | golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
391 | golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
392 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
393 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
394 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
395 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
396 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
397 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
398 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
399 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
400 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
401 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
402 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
403 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
404 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
405 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
406 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
407 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
408 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
409 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
410 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
411 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
412 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
413 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
414 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
415 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
416 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
417 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
418 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
419 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
420 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
421 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
422 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
423 | gorm.io/driver/postgres v1.3.7 h1:FKF6sIMDHDEvvMF/XJvbnCl0nu6KSKUaPXevJ4r+VYQ=
424 | gorm.io/driver/postgres v1.3.7/go.mod h1:f02ympjIcgtHEGFMZvdgTxODZ9snAHDb4hXfigBVuNI=
425 | gorm.io/driver/postgres v1.3.9 h1:lWGiVt5CijhQAg0PWB7Od1RNcBw/jS4d2cAScBcSDXg=
426 | gorm.io/driver/postgres v1.3.9/go.mod h1:qw/FeqjxmYqW5dBcYNBsnhQULIApQdk7YuuDPktVi1U=
427 | gorm.io/driver/sqlite v1.3.4 h1:NnFOPVfzi4CPsJPH4wXr6rMkPb4ElHEqKMvrsx9c9Fk=
428 | gorm.io/driver/sqlite v1.3.4/go.mod h1:B+8GyC9K7VgzJAcrcXMRPdnMcck+8FgJynEehEPM16U=
429 | gorm.io/driver/sqlite v1.3.6 h1:Fi8xNYCUplOqWiPa3/GuCeowRNBRGTf62DEmhMDHeQQ=
430 | gorm.io/driver/sqlite v1.3.6/go.mod h1:Sg1/pvnKtbQ7jLXxfZa+jSHvoX8hoZA8cn4xllOMTgE=
431 | gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
432 | gorm.io/gorm v1.23.6 h1:KFLdNgri4ExFFGTRGGFWON2P1ZN28+9SJRN8voOoYe0=
433 | gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
434 | gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
435 | gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE=
436 | gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
437 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
438 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/ElegantSoft/go-restful-generator/crud"
8 | "github.com/ElegantSoft/go-restful-generator/db"
9 | "github.com/ElegantSoft/go-restful-generator/db/models"
10 | _ "github.com/ElegantSoft/go-restful-generator/docs"
11 | "github.com/ElegantSoft/go-restful-generator/posts"
12 | "github.com/gin-contrib/cors"
13 | "github.com/gin-gonic/gin"
14 | "github.com/joho/godotenv"
15 | swaggerFiles "github.com/swaggo/files"
16 | ginSwagger "github.com/swaggo/gin-swagger"
17 | )
18 |
19 | // @contact.name API Support
20 | // @contact.url http://www.swagger.io/support
21 | // @contact.email support@swagger.io
22 |
23 | // @license.name Apache 2.0
24 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html
25 | func main() {
26 | if err := godotenv.Load(); err != nil {
27 | log.Fatal("Error loading .env file")
28 | }
29 | server := gin.New()
30 | server.Use(gin.Recovery())
31 |
32 | config := cors.DefaultConfig()
33 | // config.AllowOrigins = []string{"http://localhost:5173"}
34 | config.AllowAllOrigins = true
35 | config.AddAllowHeaders("Authorization")
36 |
37 | server.Use(cors.New(config))
38 |
39 | if os.Getenv("GIN_MODE") == "debug" {
40 | server.Use(gin.Logger())
41 | }
42 | gin.SetMode(os.Getenv("GIN_MODE"))
43 | if err := db.Open(os.Getenv("DB_URL")); err != nil {
44 | log.Fatal(err)
45 | }
46 | log.Println("server started")
47 |
48 | server.GET("", func(ctx *gin.Context) {
49 | ctx.JSON(200, gin.H{"message": "ok 5"})
50 | })
51 |
52 | // migrations
53 | db.AddUUIDExtension()
54 |
55 | if err := db.DB.AutoMigrate(
56 | models.Category{},
57 | models.Post{},
58 | ); err != nil {
59 | log.Fatal(err)
60 | }
61 |
62 | crudGroup := server.Group("crud")
63 | postsGroup := server.Group("posts")
64 |
65 | crud.RegisterRoutes(crudGroup)
66 | posts.RegisterRoutes(postsGroup)
67 |
68 | server.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
69 |
70 | // seed.SeedPosts()
71 |
72 | err := server.Run(":8081")
73 | if err != nil {
74 | log.Printf("error while starting server %+v", err)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/helpers/ensure_dir.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "errors"
5 | "os"
6 | )
7 |
8 | func EnsureDir(dirName string) error {
9 | err := os.Mkdir(dirName, os.ModeDir)
10 | if err == nil {
11 | return nil
12 | }
13 | if os.IsExist(err) {
14 | // check that the existing path is a directory
15 | info, err := os.Stat(dirName)
16 | if err != nil {
17 | return err
18 | }
19 | if !info.IsDir() {
20 | return errors.New("path exists but is not a directory")
21 | }
22 | return nil
23 | }
24 | return err
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/writetemplate/process_template.go:
--------------------------------------------------------------------------------
1 | package writetemplate
2 |
3 | import (
4 | "github.com/Masterminds/sprig"
5 | "html/template"
6 | "log"
7 | "os"
8 | )
9 |
10 | func ProcessTemplate(templatePath string, fileName string, outputPath string, data interface{}) {
11 | tmpl := template.Must(template.New("").Funcs(sprig.FuncMap()).Parse(templatePath))
12 | f, err := os.Create(outputPath)
13 | if err != nil {
14 | log.Println("create file: ", err)
15 | return
16 | }
17 | err = tmpl.Execute(f, data)
18 | if err != nil {
19 | log.Print("execute: ", err)
20 | return
21 | }
22 | log.Printf("create file: %s", fileName)
23 | //var processed bytes.Buffer
24 | //err := tmpl.ExecuteTemplate(&processed, fileName, data)
25 | //if err != nil {
26 | // log.Fatalf("Unable to parse data into template: %v\n", err)
27 | //}
28 | //formatted, err := format.Source(processed.Bytes())
29 | //if err != nil {
30 | // log.Fatalf("Could not format processed template: %v\n", err)
31 | //}
32 | //fmt.Println("Writing file: ", outputPath)
33 | //f, _ := os.Create(outputPath)
34 | //w := bufio.NewWriter(f)
35 | //w.WriteString(string(formatted))
36 | //w.Flush()
37 | }
38 |
--------------------------------------------------------------------------------
/posts/controller.go:
--------------------------------------------------------------------------------
1 | package posts
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "net/http"
7 |
8 | "github.com/ElegantSoft/go-restful-generator/common"
9 | "github.com/ElegantSoft/go-restful-generator/crud"
10 | "github.com/gin-gonic/gin"
11 | "github.com/google/uuid"
12 | )
13 |
14 | type Controller struct {
15 | service *Service
16 | }
17 |
18 | // @Success 200 {array} model
19 | // @Tags posts
20 | // @param s query string false "{'$and': [ {'title': { '$cont':'cul' } } ]}"
21 | // @param fields query string false "fields to select eg: name,age"
22 | // @param page query int false "page of pagination"
23 | // @param limit query int false "limit of pagination"
24 | // @param join query string false "join relations eg: category, parent"
25 | // @param filter query []string false "filters eg: name||$eq||ad price||$gte||200"
26 | // @param sort query []string false "filters eg: created_at,desc title,asc"
27 | // @Router /posts [get]
28 | func (c *Controller) findAll(ctx *gin.Context) {
29 | var api crud.GetAllRequest
30 | if api.Limit == 0 {
31 | api.Limit = 20
32 | }
33 | if err := ctx.ShouldBindQuery(&api); err != nil {
34 | ctx.JSON(400, gin.H{"message": err.Error()})
35 | return
36 | }
37 |
38 | var result []model
39 | var totalRows int64
40 | api.Join = api.Join + ",category"
41 | err := c.service.Find(api, &result, &totalRows)
42 | if err != nil {
43 | ctx.JSON(400, gin.H{"message": err.Error()})
44 | return
45 | }
46 |
47 | var data interface{}
48 | if api.Page > 0 {
49 | data = map[string]interface{}{
50 | "data": result,
51 | "total": totalRows,
52 | "totalPages": int(math.Ceil(float64(totalRows) / float64(api.Limit))),
53 | }
54 | } else {
55 | data = result
56 | }
57 | ctx.JSON(200, data)
58 | }
59 |
60 | // @Success 200 {object} model
61 | // @Tags posts
62 | // @param id path string true "uuid of item"
63 | // @Router /posts/{id} [get]
64 | func (c *Controller) findOne(ctx *gin.Context) {
65 | var api crud.GetAllRequest
66 | var item common.ById
67 | if err := ctx.ShouldBindQuery(&api); err != nil {
68 | ctx.JSON(400, gin.H{"message": err.Error()})
69 | return
70 | }
71 | if err := ctx.ShouldBindUri(&item); err != nil {
72 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
73 | return
74 | }
75 |
76 | api.Filter = append(api.Filter, fmt.Sprintf("id||eq||%s", item.ID))
77 |
78 | var result model
79 |
80 | err := c.service.FindOne(api, &result)
81 | if err != nil {
82 | ctx.JSON(400, gin.H{"message": err.Error()})
83 | return
84 | }
85 | ctx.JSON(200, result)
86 | }
87 |
88 | // @Success 201 {object} model
89 | // @Tags posts
90 | // @param {object} body model true "item to create"
91 | // @Router /posts [post]
92 | func (c *Controller) create(ctx *gin.Context) {
93 | var item model
94 | if err := ctx.ShouldBind(&item); err != nil {
95 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
96 | return
97 | }
98 | err := c.service.Create(&item)
99 | if err != nil {
100 | ctx.JSON(http.StatusNotFound, gin.H{"message": err.Error()})
101 | return
102 | }
103 | ctx.JSON(http.StatusCreated, gin.H{"data": item})
104 | }
105 |
106 | // @Success 200 {string} string "ok"
107 | // @Tags posts
108 | // @param id path string true "uuid of item"
109 | // @Router /posts/{id} [delete]
110 | func (c *Controller) delete(ctx *gin.Context) {
111 | var item common.ById
112 | if err := ctx.ShouldBindUri(&item); err != nil {
113 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
114 | return
115 | }
116 |
117 | id, err := uuid.ParseBytes([]byte(item.ID))
118 | if err != nil {
119 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
120 | return
121 | }
122 | err = c.service.Delete(&model{ID: id})
123 | if err != nil {
124 | ctx.JSON(http.StatusNotFound, gin.H{"message": err.Error()})
125 | return
126 | }
127 | ctx.JSON(http.StatusOK, gin.H{"message": "deleted"})
128 | }
129 |
130 | // @Success 200 {string} string "ok"
131 | // @Tags posts
132 | // @param id path string true "uuid of item"
133 | // @param item body model true "update body"
134 | // @Router /posts/{id} [put]
135 | func (c *Controller) update(ctx *gin.Context) {
136 | var item model
137 | var byId common.ById
138 | if err := ctx.ShouldBind(&item); err != nil {
139 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
140 | return
141 | }
142 | if err := ctx.ShouldBindUri(&byId); err != nil {
143 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
144 | return
145 | }
146 | id, err := uuid.Parse(byId.ID)
147 | if err != nil {
148 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
149 | return
150 | }
151 | if err := ctx.ShouldBindUri(&byId); err != nil {
152 | ctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
153 | return
154 | }
155 | err = c.service.Update(&model{ID: id}, &item)
156 | if err != nil {
157 | ctx.JSON(http.StatusNotFound, gin.H{"message": err.Error()})
158 | return
159 | }
160 | ctx.JSON(http.StatusOK, item)
161 | }
162 |
163 | func NewController(service *Service) *Controller {
164 | return &Controller{
165 | service: service,
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/posts/repository.go:
--------------------------------------------------------------------------------
1 | package posts
2 |
3 | import (
4 | "github.com/ElegantSoft/go-restful-generator/crud"
5 | "github.com/ElegantSoft/go-restful-generator/db"
6 | "github.com/ElegantSoft/go-restful-generator/db/models"
7 | )
8 |
9 | type model = models.Post
10 |
11 | type Repository struct {
12 | crud.Repository[model]
13 | }
14 |
15 | func InitRepository() *Repository {
16 | return &Repository{
17 | Repository: crud.Repository[model]{
18 | DB: db.DB,
19 | Model: model{},
20 | },
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/posts/routes.go:
--------------------------------------------------------------------------------
1 | package posts
2 |
3 | import "github.com/gin-gonic/gin"
4 |
5 | func RegisterRoutes(routerGroup *gin.RouterGroup) {
6 | service := InitService()
7 | controller := NewController(service)
8 |
9 | routerGroup.GET("", controller.findAll)
10 | routerGroup.GET(":id", controller.findOne)
11 | routerGroup.POST("", controller.create)
12 | routerGroup.DELETE(":id", controller.delete)
13 | routerGroup.PATCH(":id", controller.update)
14 | }
15 |
--------------------------------------------------------------------------------
/posts/service.go:
--------------------------------------------------------------------------------
1 | package posts
2 |
3 | import (
4 | "github.com/ElegantSoft/go-restful-generator/crud"
5 | )
6 |
7 | type Service struct {
8 | crud.Service[model]
9 | repo *Repository
10 | }
11 |
12 | func NewService(repository *Repository) *Service {
13 | return &Service{
14 | Service: *crud.NewService[model](repository),
15 | repo: repository,
16 | }
17 | }
18 |
19 | func InitService() *Service {
20 | return &Service{
21 | repo: InitRepository(),
22 | Service: *crud.NewService[model](InitRepository()),
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/requests/crud.http:
--------------------------------------------------------------------------------
1 |
2 |
3 | GET http://localhost:8080/crud?join=category&fields=title,id,category_id&filter=title||$cont||haru&filter=id||$eq||11c457ed-4fc4-4d65-b106-1b2732869ae2&sort=title&sort=id,asc
4 |
5 | ### get one
6 | GET http://localhost:8080/crud/11c457ed-4fc4-4d65-b106-1b2732869ae2?join=category
7 |
8 |
9 | #{"title": {"$cont": "hAru"}}
--------------------------------------------------------------------------------
/requests/posts.http:
--------------------------------------------------------------------------------
1 |
2 |
3 | GET http://localhost:8081/posts?page=1&filter=id||$in||11c457ed-4fc4-4d65-b106-1b2732869ae2
4 |
5 | ### get one
6 | GET http://localhost:8081/posts/11c457ed-4fc4-4d65-b106-1b2732869ae2
7 |
8 |
9 | #{"title": {"$cont": "hAru"}}
--------------------------------------------------------------------------------
/templates/db/database.tmpl:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "gorm.io/driver/sqlite"
5 | "gorm.io/gorm"
6 | "gorm.io/gorm/logger"
7 | "log"
8 | "os"
9 | "time"
10 | )
11 |
12 | var DB *gorm.DB
13 |
14 | func Open(dsn string) error {
15 | newLogger := logger.New(
16 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
17 | logger.Config{
18 | SlowThreshold: time.Second, // Slow SQL threshold
19 | LogLevel: logger.Info, // Log level
20 | Colorful: true, // Disable color
21 |
22 | },
23 | )
24 | var err error
25 | DB, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{
26 | Logger: newLogger,
27 | SkipDefaultTransaction: true,
28 | PrepareStmt: false,
29 | })
30 | if err != nil {
31 | return err
32 | }
33 | return nil
34 | }
35 |
36 | func OpenTestDB() error {
37 | newLogger := logger.New(
38 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
39 | logger.Config{
40 | SlowThreshold: time.Second, // Slow SQL threshold
41 | LogLevel: logger.Info, // Log level
42 | Colorful: true, // Disable color
43 |
44 | },
45 | )
46 | var err error
47 | DB, err = gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
48 | Logger: newLogger,
49 | SkipDefaultTransaction: true,
50 | PrepareStmt: true,
51 | })
52 | if err != nil {
53 | return err
54 | }
55 | return nil
56 | }
57 |
--------------------------------------------------------------------------------
/templates/db/migrations.tmpl:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | func AddUUIDExtension() {
4 | DB.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`)
5 | }
6 |
--------------------------------------------------------------------------------
/templates/main.tmpl:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "{{.PackageName}}/db"
7 |
8 | "github.com/gin-contrib/cors"
9 | "github.com/gin-gonic/gin"
10 | "github.com/joho/godotenv"
11 | swaggerFiles "github.com/swaggo/files"
12 | ginSwagger "github.com/swaggo/gin-swagger"
13 | "{{.PackageName}}/db/models"
14 | _ "{{.PackageName}}/docs"
15 | )
16 |
17 | // @contact.name API Support
18 | // @contact.url http://www.swagger.io/support
19 | // @contact.email support@swagger.io
20 |
21 | // @license.name Apache 2.0
22 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html
23 | func main() {
24 | if err := godotenv.Load(); err != nil {
25 | log.Fatal("Error loading .env file")
26 | }
27 | server := gin.New()
28 | server.Use(gin.Recovery())
29 |
30 | config := cors.DefaultConfig()
31 | // config.AllowOrigins = []string{"http://localhost:5173"}
32 | config.AllowAllOrigins = true
33 | config.AddAllowHeaders("Authorization")
34 |
35 | server.Use(cors.New(config))
36 |
37 | if os.Getenv("GIN_MODE") == "debug" {
38 | server.Use(gin.Logger())
39 | }
40 | gin.SetMode(os.Getenv("GIN_MODE"))
41 |
42 | if err := db.Open(os.Getenv("DB_URL")); err != nil {
43 | log.Fatal(err)
44 | }
45 | log.Println("server started")
46 |
47 | server.GET("", func(ctx *gin.Context) {
48 | ctx.JSON(200, gin.H{"message": "ok 5"})
49 | })
50 |
51 | // migrations
52 | db.AddUUIDExtension()
53 |
54 | if err := db.DB.AutoMigrate(
55 | models.Category{},
56 | models.Post{},
57 | ); err != nil {
58 | log.Fatal(err)
59 | }
60 |
61 | server.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
62 |
63 |
64 | err := server.Run()
65 | if err != nil {
66 | log.Printf("error while starting server %+v", err)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------