go-duck-cli 1.2.11 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/README.md +79 -0
  2. package/generators/ai_docs.js +22 -8
  3. package/generators/config.js +16 -8
  4. package/generators/devops.js +591 -1
  5. package/generators/postman.js +156 -6
  6. package/generators/security.js +2 -2
  7. package/go-duck-planner/css/bootstrap.min.css +6 -0
  8. package/go-duck-planner/css/main.css +1 -0
  9. package/go-duck-planner/favicon.ico +0 -0
  10. package/go-duck-planner/fonts/icomoonf33b-2.html +0 -0
  11. package/go-duck-planner/fonts/icomoonf33b-3.html +0 -0
  12. package/go-duck-planner/fonts/icomoonf33b-4.html +0 -0
  13. package/go-duck-planner/fonts/icomoonf33b.html +0 -0
  14. package/go-duck-planner/fonts/open-sans/OpenSans-Bold.html +0 -0
  15. package/go-duck-planner/fonts/open-sans/OpenSans-ExtraBold.html +0 -0
  16. package/go-duck-planner/fonts/open-sans/OpenSans-Light.html +0 -0
  17. package/go-duck-planner/fonts/open-sans/OpenSans-Regular.html +0 -0
  18. package/go-duck-planner/fonts/open-sans/OpenSans-SemiBold.html +0 -0
  19. package/go-duck-planner/fonts/rubik-mono/RubikMonoOne-Regular.html +0 -0
  20. package/go-duck-planner/fonts/style.css +2735 -0
  21. package/go-duck-planner/img/cancel.html +0 -0
  22. package/go-duck-planner/img/consulting.html +0 -0
  23. package/go-duck-planner/img/consulting2.html +0 -0
  24. package/go-duck-planner/img/dashed-circles-dark.html +0 -0
  25. package/go-duck-planner/img/docs/doc.html +0 -0
  26. package/go-duck-planner/img/docs/pdf.html +0 -0
  27. package/go-duck-planner/img/docs/ppt.html +0 -0
  28. package/go-duck-planner/img/docs/xls.html +0 -0
  29. package/go-duck-planner/img/docs/zip.html +0 -0
  30. package/go-duck-planner/img/empty-chat-display.html +0 -0
  31. package/go-duck-planner/img/error-screen/stars.html +0 -0
  32. package/go-duck-planner/img/fav.png +0 -0
  33. package/go-duck-planner/img/flags/1x1/au.html +0 -0
  34. package/go-duck-planner/img/flags/1x1/br.html +0 -0
  35. package/go-duck-planner/img/flags/1x1/bv.html +131 -0
  36. package/go-duck-planner/img/flags/1x1/ca.html +0 -0
  37. package/go-duck-planner/img/flags/1x1/cw.html +131 -0
  38. package/go-duck-planner/img/flags/1x1/eu.html +131 -0
  39. package/go-duck-planner/img/flags/1x1/hn.html +131 -0
  40. package/go-duck-planner/img/flags/1x1/in.html +0 -0
  41. package/go-duck-planner/img/flags/1x1/ni.html +131 -0
  42. package/go-duck-planner/img/flags/1x1/se.html +131 -0
  43. package/go-duck-planner/img/flags/1x1/za.html +131 -0
  44. package/go-duck-planner/img/folder.html +0 -0
  45. package/go-duck-planner/img/followers.html +0 -0
  46. package/go-duck-planner/img/food/biscuit.html +0 -0
  47. package/go-duck-planner/img/food/cupcake.html +0 -0
  48. package/go-duck-planner/img/food/donut.html +0 -0
  49. package/go-duck-planner/img/food/ice-cream.html +0 -0
  50. package/go-duck-planner/img/food/pizza.html +0 -0
  51. package/go-duck-planner/img/food/tea-cup.html +0 -0
  52. package/go-duck-planner/img/hover-on-tabs.html +0 -0
  53. package/go-duck-planner/img/icon-search.html +0 -0
  54. package/go-duck-planner/img/lines-bg.html +0 -0
  55. package/go-duck-planner/img/lines-bg2.html +0 -0
  56. package/go-duck-planner/img/login-bg.html +0 -0
  57. package/go-duck-planner/img/logo-compact.html +0 -0
  58. package/go-duck-planner/img/logo-large.html +0 -0
  59. package/go-duck-planner/img/logo.png +0 -0
  60. package/go-duck-planner/img/logo.svg +19 -0
  61. package/go-duck-planner/img/pattern.html +0 -0
  62. package/go-duck-planner/img/products/bag.html +0 -0
  63. package/go-duck-planner/img/products/camera.html +0 -0
  64. package/go-duck-planner/img/products/clock.html +0 -0
  65. package/go-duck-planner/img/products/drone.html +0 -0
  66. package/go-duck-planner/img/products/pencils.html +0 -0
  67. package/go-duck-planner/img/products/ring.html +0 -0
  68. package/go-duck-planner/img/products/shirt.html +0 -0
  69. package/go-duck-planner/img/products/shoes.html +0 -0
  70. package/go-duck-planner/img/products/toy.html +0 -0
  71. package/go-duck-planner/img/realestate/img1.html +0 -0
  72. package/go-duck-planner/img/realestate/img10.html +0 -0
  73. package/go-duck-planner/img/realestate/img11.html +0 -0
  74. package/go-duck-planner/img/realestate/img12.html +0 -0
  75. package/go-duck-planner/img/realestate/img2.html +0 -0
  76. package/go-duck-planner/img/realestate/img3.html +0 -0
  77. package/go-duck-planner/img/realestate/img4.html +0 -0
  78. package/go-duck-planner/img/realestate/img5.html +0 -0
  79. package/go-duck-planner/img/realestate/img6.html +0 -0
  80. package/go-duck-planner/img/realestate/img7.html +0 -0
  81. package/go-duck-planner/img/realestate/img8.html +0 -0
  82. package/go-duck-planner/img/realestate/img9.html +0 -0
  83. package/go-duck-planner/img/shade.html +0 -0
  84. package/go-duck-planner/img/stock/img1.html +0 -0
  85. package/go-duck-planner/img/stock/img10.html +0 -0
  86. package/go-duck-planner/img/stock/img11.html +0 -0
  87. package/go-duck-planner/img/stock/img12.html +0 -0
  88. package/go-duck-planner/img/stock/img13.html +0 -0
  89. package/go-duck-planner/img/stock/img14.html +0 -0
  90. package/go-duck-planner/img/stock/img2.html +0 -0
  91. package/go-duck-planner/img/stock/img3.html +0 -0
  92. package/go-duck-planner/img/stock/img4.html +0 -0
  93. package/go-duck-planner/img/stock/img5.html +0 -0
  94. package/go-duck-planner/img/stock/img6.html +0 -0
  95. package/go-duck-planner/img/stock/img7.html +0 -0
  96. package/go-duck-planner/img/stock/img8.html +0 -0
  97. package/go-duck-planner/img/stock/img9.html +0 -0
  98. package/go-duck-planner/img/svg/box.html +0 -0
  99. package/go-duck-planner/img/svg/commisions.html +0 -0
  100. package/go-duck-planner/img/svg/customer.html +0 -0
  101. package/go-duck-planner/img/svg/income.html +0 -0
  102. package/go-duck-planner/img/trophy.html +0 -0
  103. package/go-duck-planner/img/user-2.html +0 -0
  104. package/go-duck-planner/img/user.html +0 -0
  105. package/go-duck-planner/img/user1.html +0 -0
  106. package/go-duck-planner/img/user10.html +0 -0
  107. package/go-duck-planner/img/user12.html +0 -0
  108. package/go-duck-planner/img/user13.html +0 -0
  109. package/go-duck-planner/img/user14.html +0 -0
  110. package/go-duck-planner/img/user15.html +0 -0
  111. package/go-duck-planner/img/user16.html +0 -0
  112. package/go-duck-planner/img/user17.html +0 -0
  113. package/go-duck-planner/img/user18.html +0 -0
  114. package/go-duck-planner/img/user19.html +0 -0
  115. package/go-duck-planner/img/user2.html +0 -0
  116. package/go-duck-planner/img/user20.html +0 -0
  117. package/go-duck-planner/img/user21.html +0 -0
  118. package/go-duck-planner/img/user22.html +0 -0
  119. package/go-duck-planner/img/user23.html +0 -0
  120. package/go-duck-planner/img/user24.html +0 -0
  121. package/go-duck-planner/img/user3.html +0 -0
  122. package/go-duck-planner/img/user4.html +0 -0
  123. package/go-duck-planner/img/user5.html +0 -0
  124. package/go-duck-planner/img/user6.html +0 -0
  125. package/go-duck-planner/img/user7.html +0 -0
  126. package/go-duck-planner/img/user8.html +0 -0
  127. package/go-duck-planner/img/user9.html +0 -0
  128. package/go-duck-planner/index.html +30 -0
  129. package/go-duck-planner/js/bootstrap.bundle.min.html +0 -0
  130. package/go-duck-planner/js/jquery.min.html +0 -0
  131. package/go-duck-planner/js/main.html +0 -0
  132. package/go-duck-planner/js/modernizr.html +0 -0
  133. package/go-duck-planner/js/moment.html +0 -0
  134. package/go-duck-planner/main-HHOZYGRI.js +47 -0
  135. package/go-duck-planner/styles-3LK4NBNX.css +1 -0
  136. package/go-duck-planner/vendor/apex/apexcharts.min.html +0 -0
  137. package/go-duck-planner/vendor/apex/custom/analytics/byChannelGraph.html +0 -0
  138. package/go-duck-planner/vendor/apex/custom/analytics/byCountryGraph.html +0 -0
  139. package/go-duck-planner/vendor/apex/custom/analytics/byDeviceGraph.html +0 -0
  140. package/go-duck-planner/vendor/apex/custom/analytics/ordersGraph.html +0 -0
  141. package/go-duck-planner/vendor/apex/custom/analytics/targets.html +0 -0
  142. package/go-duck-planner/vendor/apex/custom/crm/sales.html +0 -0
  143. package/go-duck-planner/vendor/apex/custom/crm/sparkline-graphs.html +0 -0
  144. package/go-duck-planner/vendor/apex/custom/home/customersGraph.html +0 -0
  145. package/go-duck-planner/vendor/apex/custom/home/earningsGraph.html +0 -0
  146. package/go-duck-planner/vendor/apex/custom/home/ordersGraph.html +0 -0
  147. package/go-duck-planner/vendor/apex/custom/home/salesGraph.html +0 -0
  148. package/go-duck-planner/vendor/apex/custom/home/sparkline.html +0 -0
  149. package/go-duck-planner/vendor/apex/custom/home/visitorsGraph.html +0 -0
  150. package/go-duck-planner/vendor/apex/custom/profile/revenue.html +0 -0
  151. package/go-duck-planner/vendor/apex/custom/reports/orders.html +0 -0
  152. package/go-duck-planner/vendor/apex/custom/reports/revenue.html +0 -0
  153. package/go-duck-planner/vendor/apex/custom/reports/sales.html +0 -0
  154. package/go-duck-planner/vendor/apex/custom/reports/sales2.html +0 -0
  155. package/go-duck-planner/vendor/apex/custom/reports/sparkline.html +0 -0
  156. package/go-duck-planner/vendor/apex/custom/saas/revenue.html +0 -0
  157. package/go-duck-planner/vendor/apex/custom/saas/sales.html +0 -0
  158. package/go-duck-planner/vendor/apex/custom/sales/revenue.html +0 -0
  159. package/go-duck-planner/vendor/apex/custom/sales/tickets.html +0 -0
  160. package/go-duck-planner/vendor/bs-select/bs-select-custom.html +0 -0
  161. package/go-duck-planner/vendor/bs-select/bs-select.html +0 -0
  162. package/go-duck-planner/vendor/bs-select/bs-select.min.html +0 -0
  163. package/go-duck-planner/vendor/calendar/css/custom.html +0 -0
  164. package/go-duck-planner/vendor/calendar/css/main.min.html +0 -0
  165. package/go-duck-planner/vendor/calendar/custom/custom-schedule-calendar.html +0 -0
  166. package/go-duck-planner/vendor/calendar/custom/daygrid-calendar.html +0 -0
  167. package/go-duck-planner/vendor/calendar/custom/draggable-calendar.html +0 -0
  168. package/go-duck-planner/vendor/calendar/custom/google-view-calendar.html +0 -0
  169. package/go-duck-planner/vendor/calendar/custom/list-view-calendar.html +0 -0
  170. package/go-duck-planner/vendor/calendar/custom/selectable-calendar.html +0 -0
  171. package/go-duck-planner/vendor/calendar/js/main.min.html +0 -0
  172. package/go-duck-planner/vendor/circliful/circliful.custom.html +0 -0
  173. package/go-duck-planner/vendor/circliful/circliful.min.html +0 -0
  174. package/go-duck-planner/vendor/datatables/buttons.bs.html +0 -0
  175. package/go-duck-planner/vendor/datatables/buttons.min.html +0 -0
  176. package/go-duck-planner/vendor/datatables/buttons.print.min.html +0 -0
  177. package/go-duck-planner/vendor/datatables/custom/custom-datatables.html +0 -0
  178. package/go-duck-planner/vendor/datatables/dataTables.bootstrap.min.html +0 -0
  179. package/go-duck-planner/vendor/datatables/dataTables.bs4-custom.html +0 -0
  180. package/go-duck-planner/vendor/datatables/dataTables.bs4.html +0 -0
  181. package/go-duck-planner/vendor/datatables/dataTables.min.html +0 -0
  182. package/go-duck-planner/vendor/datatables/html5.min.html +0 -0
  183. package/go-duck-planner/vendor/datatables/jszip.min.html +0 -0
  184. package/go-duck-planner/vendor/datatables/pdfmake.min.html +0 -0
  185. package/go-duck-planner/vendor/datatables/vfs_fonts.html +0 -0
  186. package/go-duck-planner/vendor/daterange/custom-daterange.html +0 -0
  187. package/go-duck-planner/vendor/daterange/daterange-2.html +0 -0
  188. package/go-duck-planner/vendor/daterange/daterange.html +0 -0
  189. package/go-duck-planner/vendor/dropzone/dropzone.min-2.html +0 -0
  190. package/go-duck-planner/vendor/dropzone/dropzone.min.html +0 -0
  191. package/go-duck-planner/vendor/gallery/baguetteBox.html +0 -0
  192. package/go-duck-planner/vendor/gallery/custom-gallery.html +0 -0
  193. package/go-duck-planner/vendor/gallery/gallery.html +0 -0
  194. package/go-duck-planner/vendor/gallery/plugins.html +0 -0
  195. package/go-duck-planner/vendor/input-tags/tagsinput-custom.html +0 -0
  196. package/go-duck-planner/vendor/input-tags/tagsinput.html +0 -0
  197. package/go-duck-planner/vendor/input-tags/tagsinput.min.html +0 -0
  198. package/go-duck-planner/vendor/input-tags/typeahead.html +0 -0
  199. package/go-duck-planner/vendor/megamenu/css/megamenu.css +591 -0
  200. package/go-duck-planner/vendor/megamenu/js/custom.html +0 -0
  201. package/go-duck-planner/vendor/megamenu/js/megamenu.html +0 -0
  202. package/go-duck-planner/vendor/rating/raty-custom.html +0 -0
  203. package/go-duck-planner/vendor/rating/raty.html +0 -0
  204. package/go-duck-planner/vendor/search-filter/custom-search-filter.css +122 -0
  205. package/go-duck-planner/vendor/search-filter/custom-search-filter.html +0 -0
  206. package/go-duck-planner/vendor/search-filter/search-filter.css +497 -0
  207. package/go-duck-planner/vendor/search-filter/search-filter.html +0 -0
  208. package/go-duck-planner/vendor/slimscroll/custom-scrollbar.html +0 -0
  209. package/go-duck-planner/vendor/slimscroll/slimscroll.min.html +0 -0
  210. package/go-duck-planner/vendor/summernote/summernote-bs4-2.html +0 -0
  211. package/go-duck-planner/vendor/summernote/summernote-bs4.html +0 -0
  212. package/go-duck-planner/vendor/wizard/jquery.steps.custom.html +0 -0
  213. package/go-duck-planner/vendor/wizard/jquery.steps.html +0 -0
  214. package/go-duck-planner/vendor/wizard/jquery.steps.min.html +0 -0
  215. package/index.js +98 -9
  216. package/package.json +1 -1
  217. package/parser/gdl.js +2 -0
  218. package/templates/docs/index.html.hbs +13 -1
  219. package/templates/docs/pages/cli.hbs +20 -4
  220. package/templates/docs/pages/gdl-annotations.hbs +17 -0
  221. package/templates/docs/pages/rest.hbs +21 -0
  222. package/templates/go/controller.go.hbs +107 -20
  223. package/templates/go/main.go.hbs +16 -2
  224. package/templates/go/router.go.hbs +8 -3
  225. package/templates/kratos/service.go.hbs +25 -2
  226. package/templates/proto/entity.proto.hbs +1 -0
@@ -69,7 +69,8 @@ go run main.go</code></pre>
69
69
  <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">1. REST APIs & Generic Search</h2>
70
70
  <p class="mb-4">The application provides standard RESTful CRUD endpoints for all generated entities (e.g., {{#if entities.length}}{{entities.[0].name}}{{else}}Entity{{/if}}).</p>
71
71
 
72
- <h3 class="font-semibold mb-2">Standard CRUD:</h3>
72
+ <h3 class="font-semibold mb-2">Standard CRUD (Multi-Protocol):</h3>
73
+ <p class="mb-2 text-sm text-gray-600">The REST API natively supports JSON and MessagePack based on your <code>application.yml</code> settings.</p>
73
74
  <pre><code class="language-http">GET /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}entities{{/if}}?page=1&pageSize=10
74
75
  POST /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}entities{{/if}}
75
76
  PUT /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}entities{{/if}}/:id
@@ -138,6 +139,17 @@ mosquitto_sub -h localhost -p {{mqttPort}} -t "go-duck/events/#" -u dev_user -P
138
139
  <p class="text-sm mt-2">Example topic: <code>go-duck/events/{{#if entities.length}}{{toLowerCase entities.[0].name}}{{else}}entity{{/if}}/CREATE</code></p>
139
140
  </section>
140
141
 
142
+ <!-- gRPC-Web -->
143
+ <section id="grpc-web" class="content-section">
144
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">gRPC & gRPC-Web</h2>
145
+ <p class="mb-4">The Kratos gRPC engine powers blazing fast service-to-service communication. For frontend developers, we automatically start a <strong>gRPC-Web Proxy</strong>.</p>
146
+ <ul class="list-disc pl-6 space-y-2 mb-4">
147
+ <li>The core Kratos gRPC server runs on <code>:9000</code> (configurable).</li>
148
+ <li>The <strong>gRPC-Web Proxy</strong> runs on <code>:9090</code>, translating HTTP/1.1 calls to HTTP/2.</li>
149
+ <li>Frontend apps (React/Angular) can directly call Protobuf endpoints using standard <code>grpc-web</code> generated clients without hitting the REST APIs!</li>
150
+ </ul>
151
+ </section>
152
+
141
153
  <!-- Kratos gRPC -->
142
154
  <section id="grpc-kratos" class="content-section">
143
155
  <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Kratos Secured gRPC APIs</h2>
@@ -44,17 +44,33 @@ go-duck import-gdl my_new_schema.gdl -o ./MY_APP</code></pre>
44
44
  <span class="w-8 h-8 rounded-lg bg-emerald-100 text-emerald-600 flex items-center justify-center mr-3 text-sm">
45
45
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2"></path></svg>
46
46
  </span>
47
- 2. Persistence Intelligence & the `.go-duck/` Folder
47
+ 2. Schema Evolution & the `.go-duck/` State
48
48
  </h2>
49
- <p class="mb-4 text-slate-600 leading-relaxed font-medium">"If I run the generator twice, will it overwrite my database migrations?" <strong>No.</strong></p>
49
+ <p class="mb-4 text-slate-600 leading-relaxed font-medium">"If I run the generator to import a new GDL, will it overwrite my database or delete existing entities?" <strong>No, thanks to stateful evolution.</strong></p>
50
50
 
51
- <p class="mb-6 text-slate-600 leading-relaxed">The generator maintains a stateful snapshot of every entity it has ever generated inside the hidden <code class="bg-slate-100 px-1 py-0.5 rounded text-slate-800 font-mono text-sm">.go-duck/</code> directory at the root of your target project. When you run <code>import-gdl</code>, the code parser literally diffs your new file against the JSON representations in <code>.go-duck/</code> to intelligently assess the exact Table changes necessary without executing ghost migrations.</p>
51
+ <p class="mb-6 text-slate-600 leading-relaxed">The generator maintains a stateful snapshot of every entity it has ever generated inside the hidden <code class="bg-slate-100 px-1 py-0.5 rounded text-slate-800 font-mono text-sm">.go-duck/</code> directory at the root of your target project. When you run <code>import-gdl</code>, the CLI intelligently merges existing entities with newly parsed entities and executes targeted diff operations:</p>
52
+
53
+ <!-- EVOLUTION TYPE CARDS -->
54
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
55
+ <div class="p-6 bg-slate-50 border border-slate-200 rounded-2xl">
56
+ <span class="text-xs font-bold text-indigo-600 uppercase tracking-wide block mb-2 font-mono">1. Snapshot Merging</span>
57
+ <p class="text-xs text-slate-600 leading-relaxed m-0">Supports splitting your entities into multiple GDL files. Unspecified active models are loaded from the <code>.go-duck/</code> folder and merged with new entities, keeping active routers and endpoints in sync.</p>
58
+ </div>
59
+ <div class="p-6 bg-slate-50 border border-slate-200 rounded-2xl">
60
+ <span class="text-xs font-bold text-emerald-600 uppercase tracking-wide block mb-2 font-mono">2. Column Alterations</span>
61
+ <p class="text-xs text-slate-600 leading-relaxed m-0">Adding, dropping, or modifying fields inside entity blocks generates specific <code>ADD COLUMN</code> or <code>DROP COLUMN</code> SQL statements in a timestamped Goose migration file.</p>
62
+ </div>
63
+ <div class="p-6 bg-slate-50 border border-slate-200 rounded-2xl">
64
+ <span class="text-xs font-bold text-rose-600 uppercase tracking-wide block mb-2 font-mono">3. Complete Purging</span>
65
+ <p class="text-xs text-slate-600 leading-relaxed m-0">Marking an entity with the <code>@Delete</code> annotation automatically triggers a database <code>DROP TABLE</code> SQL migration, purges all generated Go/Protobuf code files, and clears its snapshot.</p>
66
+ </div>
67
+ </div>
52
68
 
53
69
  <div class="bg-rose-50 border border-rose-200 p-5 mb-6 rounded-xl flex items-start shadow-sm shadow-rose-100/50">
54
70
  <svg class="w-6 h-6 text-rose-500 mt-0.5 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
55
71
  <div>
56
72
  <h4 class="font-bold text-rose-900 mb-1">Warning</h4>
57
- <p class="text-rose-800 text-sm leading-relaxed"><strong>Never delete `.go-duck/`</strong> unless you are intentionally wiping the database and starting configuration completely from zero.</p>
73
+ <p class="text-rose-800 text-sm leading-relaxed"><strong>Never manually delete `.go-duck/`</strong> unless you are intentionally wiping the entire database state and starting configuration completely from scratch.</p>
58
74
  </div>
59
75
  </div>
60
76
  </section>
@@ -100,6 +100,23 @@
100
100
  <p class="text-[11px] text-indigo-900 m-0 font-medium font-mono leading-tight">Architectural Impact: Scaffolds routes into the <code class="text-indigo-600 text-[10px]">/api/open/*</code> group, bypassing the OIDC Validator middleware chain.</p>
101
101
  </div>
102
102
  </div>
103
+
104
+ <!-- @Delete -->
105
+ <div class="p-10 bg-rose-50 border border-rose-100 rounded-[3rem] shadow-sm transition-all duration-300 hover:shadow-rose-200 group relative overflow-hidden">
106
+ <div class="absolute top-0 right-0 p-6 opacity-[0.03] group-hover:opacity-[0.07] transition-opacity">
107
+ <svg class="w-32 h-32 text-rose-900" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
108
+ </div>
109
+ <div class="flex items-center justify-between mb-8">
110
+ <code class="text-rose-700 font-mono font-black text-2xl group-hover:scale-105 transition-transform">@Delete</code>
111
+ <div class="w-12 h-12 bg-white rounded-2xl flex items-center justify-center text-2xl shadow-sm">🗑️</div>
112
+ </div>
113
+ <h4 class="text-lg font-black text-slate-900 mb-4 m-0 uppercase tracking-tighter">Entity Deletion</h4>
114
+ <p class="text-sm text-slate-700 leading-relaxed m-0 italic mb-6">Triggers full entity cleanup. Upon import, the generator purges all generated source code (models, controllers, repositories) and generates a database <code>DROP TABLE</code> migration.</p>
115
+ <div class="p-4 bg-white/50 rounded-2xl border border-rose-200 border-dashed">
116
+ <span class="text-[10px] font-bold text-rose-400 uppercase tracking-widest block mb-2 font-mono">Architectural Impact</span>
117
+ <p class="text-[11px] text-rose-900 m-0 font-medium font-mono leading-tight">Removes snapshots from .go-duck/, wipes Go files, and scaffolds drop SQL statements.</p>
118
+ </div>
119
+ </div>
103
120
  </div>
104
121
 
105
122
  <!-- COMPLEX EXAMPLE -->
@@ -129,6 +129,27 @@
129
129
  </div>
130
130
  </section>
131
131
 
132
+ <!-- PAGINATION & SORTING -->
133
+ <section class="mb-20">
134
+ <h2 class="text-3xl font-black text-slate-900 mb-8 tracking-tight italic underline decoration-indigo-600 underline-offset-8">Pagination & Dynamic Sorting</h2>
135
+ <div class="p-8 bg-slate-50 border border-slate-200 rounded-[2.5rem] shadow-sm">
136
+ <p class="text-slate-600 mb-6">List endpoints support pagination and dynamic sorting for both relational (PostgreSQL) and document-based (MongoDB) silos.</p>
137
+
138
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
139
+ <div class="p-6 bg-white rounded-2xl border border-slate-100 shadow-sm border-l-4 border-l-indigo-600">
140
+ <h4 class="font-bold text-slate-900 mb-2">Pagination</h4>
141
+ <p class="text-xs text-slate-500 mb-4">Specify the page number (1-indexed) and limit size via standard query parameters. The response contains total count in the <code>X-Total-Count</code> header.</p>
142
+ <code class="text-xs font-mono font-bold text-indigo-700 bg-indigo-50 px-3 py-1 rounded-full">GET /api/v1/car?page=1&amp;size=20</code>
143
+ </div>
144
+ <div class="p-6 bg-white rounded-2xl border border-slate-100 shadow-sm border-l-4 border-l-emerald-600">
145
+ <h4 class="font-bold text-slate-900 mb-2">Dynamic Sorting</h4>
146
+ <p class="text-xs text-slate-500 mb-4">Sort fields dynamically in ascending (default or <code>asc</code>) or descending (<code>desc</code>) order. The sorting format is <code>?sort=fieldname,direction</code>.</p>
147
+ <code class="text-xs font-mono font-bold text-emerald-700 bg-emerald-50 px-3 py-1 rounded-full">GET /api/v1/car?sort=price,desc</code>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </section>
152
+
132
153
  <!-- RPC OPERATORS -->
133
154
  <section class="mb-10 text-center">
134
155
  <h2 class="text-2xl font-black text-slate-900 mb-6 italic italic underline decoration-indigo-200 underline-offset-8 decoration-8 font-serif uppercase tracking-widest">GORM Filter Reference</h2>
@@ -3,6 +3,7 @@ package controllers
3
3
  import (
4
4
  "net/http"
5
5
  "strconv"
6
+ "strings"
6
7
  {{#if isSearchable}}
7
8
  "context"
8
9
  {{/if}}
@@ -22,6 +23,8 @@ import (
22
23
  "{{app_name}}/internal/search"
23
24
  {{/if}}
24
25
  "github.com/gin-gonic/gin"
26
+ "github.com/gin-gonic/gin/binding"
27
+ "github.com/gin-gonic/gin/render"
25
28
  {{#if isDocument}}
26
29
  "go.mongodb.org/mongo-driver/bson"
27
30
  "go.mongodb.org/mongo-driver/mongo"
@@ -44,9 +47,16 @@ type {{capitalize name}}Controller struct {
44
47
  func (ctrl *{{capitalize name}}Controller) Create(c *gin.Context) {
45
48
  ctx := c.Request.Context()
46
49
  var entity models.{{capitalize name}}
47
- if err := c.ShouldBindJSON(&entity); err != nil {
48
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
49
- return
50
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
51
+ if err := c.ShouldBindWith(&entity, binding.MsgPack); err != nil {
52
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
53
+ return
54
+ }
55
+ } else {
56
+ if err := c.ShouldBindJSON(&entity); err != nil {
57
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
58
+ return
59
+ }
50
60
  }
51
61
 
52
62
  {{#if isDocument}}
@@ -138,7 +148,11 @@ func (ctrl *{{capitalize name}}Controller) Create(c *gin.Context) {
138
148
  }
139
149
  {{/if}}
140
150
 
141
- c.JSON(http.StatusCreated, entity)
151
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
152
+ c.Render(http.StatusCreated, render.MsgPack{Data: entity})
153
+ } else {
154
+ c.JSON(http.StatusCreated, entity)
155
+ }
142
156
  }
143
157
 
144
158
  // GetAll handles parallel read aggregation for both SQL and Mongo
@@ -146,6 +160,7 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
146
160
  ctx := c.Request.Context()
147
161
  page, _ := strconv.Atoi(c.DefaultQuery("page", "0"))
148
162
  size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
163
+ sortParam := c.DefaultQuery("sort", "")
149
164
 
150
165
  offset := 0
151
166
  if page > 0 {
@@ -169,6 +184,15 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
169
184
  defer wg.Done()
170
185
  var entities []models.{{capitalize name}}
171
186
  opts := options.Find().SetSkip(int64(offset)).SetLimit(int64(size))
187
+ if sortParam != "" {
188
+ parts := strings.Split(sortParam, ",")
189
+ field := parts[0]
190
+ order := 1
191
+ if len(parts) > 1 && strings.ToLower(parts[1]) == "desc" {
192
+ order = -1
193
+ }
194
+ opts.SetSort(bson.D{bson.E{Key: field, Value: order}})
195
+ }
172
196
  cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
173
197
  if err == nil {
174
198
  cursor.All(ctx, &entities)
@@ -179,7 +203,11 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
179
203
  }(role, siloDB)
180
204
  }
181
205
  wg.Wait()
182
- c.JSON(http.StatusOK, federatedData)
206
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
207
+ c.Render(http.StatusOK, render.MsgPack{Data: federatedData})
208
+ } else {
209
+ c.JSON(http.StatusOK, federatedData)
210
+ }
183
211
  return
184
212
  }
185
213
  {{/if}}
@@ -200,13 +228,26 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
200
228
 
201
229
  var entities []models.{{capitalize name}}
202
230
  opts := options.Find().SetSkip(int64(offset)).SetLimit(int64(size))
231
+ if sortParam != "" {
232
+ parts := strings.Split(sortParam, ",")
233
+ field := parts[0]
234
+ order := 1
235
+ if len(parts) > 1 && strings.ToLower(parts[1]) == "desc" {
236
+ order = -1
237
+ }
238
+ opts.SetSort(bson.D{bson.E{Key: field, Value: order}})
239
+ }
203
240
  cursor, err := tenantDB.Collection("{{toLowerCase name}}s").Find(ctx, filter, opts)
204
241
  if err != nil {
205
242
  c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
206
243
  return
207
244
  }
208
245
  cursor.All(ctx, &entities)
209
- c.JSON(http.StatusOK, entities)
246
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
247
+ c.Render(http.StatusOK, render.MsgPack{Data: entities})
248
+ } else {
249
+ c.JSON(http.StatusOK, entities)
250
+ }
210
251
 
211
252
  {{else}}
212
253
  // 🦆 GORM Path
@@ -227,7 +268,11 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
227
268
  go func(r string, db *gorm.DB) {
228
269
  defer wg.Done()
229
270
  var entities []models.{{capitalize name}}
230
- if err := db.WithContext(ctx).Offset(offset).Limit(size).Find(&entities).Error; err == nil {
271
+ query := db.WithContext(ctx)
272
+ if sortParam != "" {
273
+ query = query.Order(strings.ReplaceAll(sortParam, ",", " "))
274
+ }
275
+ if err := query.Offset(offset).Limit(size).Find(&entities).Error; err == nil {
231
276
  mu.Lock()
232
277
  federatedData[r] = entities
233
278
  mu.Unlock()
@@ -235,7 +280,11 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
235
280
  }(role, siloDB)
236
281
  }
237
282
  wg.Wait()
238
- c.JSON(http.StatusOK, federatedData)
283
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
284
+ c.Render(http.StatusOK, render.MsgPack{Data: federatedData})
285
+ } else {
286
+ c.JSON(http.StatusOK, federatedData)
287
+ }
239
288
  return
240
289
  }
241
290
  {{/if}}
@@ -248,11 +297,19 @@ func (ctrl *{{capitalize name}}Controller) GetAll(c *gin.Context) {
248
297
  c.Header("X-Total-Count", strconv.FormatInt(totalCount, 10))
249
298
 
250
299
  var entities []models.{{capitalize name}}
251
- if err := tenantDB.WithContext(ctx).Offset(offset).Limit(size).Find(&entities).Error; err != nil {
300
+ query := tenantDB.WithContext(ctx)
301
+ if sortParam != "" {
302
+ query = query.Order(strings.ReplaceAll(sortParam, ",", " "))
303
+ }
304
+ if err := query.Offset(offset).Limit(size).Find(&entities).Error; err != nil {
252
305
  c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
253
306
  return
254
307
  }
255
- c.JSON(http.StatusOK, entities)
308
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
309
+ c.Render(http.StatusOK, render.MsgPack{Data: entities})
310
+ } else {
311
+ c.JSON(http.StatusOK, entities)
312
+ }
256
313
  {{/if}}
257
314
  }
258
315
 
@@ -269,7 +326,11 @@ func (ctrl *{{capitalize name}}Controller) GetByID(c *gin.Context) {
269
326
  c.JSON(http.StatusNotFound, gin.H{"error": "Not found"})
270
327
  return
271
328
  }
272
- c.JSON(http.StatusOK, entity)
329
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
330
+ c.Render(http.StatusOK, render.MsgPack{Data: entity})
331
+ } else {
332
+ c.JSON(http.StatusOK, entity)
333
+ }
273
334
  {{else}}
274
335
  db, _ := c.Get("tenantDBConn")
275
336
  tenantDB := db.(*gorm.DB)
@@ -278,7 +339,11 @@ func (ctrl *{{capitalize name}}Controller) GetByID(c *gin.Context) {
278
339
  c.JSON(http.StatusNotFound, gin.H{"error": "Not found"})
279
340
  return
280
341
  }
281
- c.JSON(http.StatusOK, entity)
342
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
343
+ c.Render(http.StatusOK, render.MsgPack{Data: entity})
344
+ } else {
345
+ c.JSON(http.StatusOK, entity)
346
+ }
282
347
  {{/if}}
283
348
  }
284
349
 
@@ -286,9 +351,16 @@ func (ctrl *{{capitalize name}}Controller) Update(c *gin.Context) {
286
351
  ctx := c.Request.Context()
287
352
  id := c.Param("id")
288
353
  var entity models.{{capitalize name}}
289
- if err := c.ShouldBindJSON(&entity); err != nil {
290
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
291
- return
354
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
355
+ if err := c.ShouldBindWith(&entity, binding.MsgPack); err != nil {
356
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
357
+ return
358
+ }
359
+ } else {
360
+ if err := c.ShouldBindJSON(&entity); err != nil {
361
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
362
+ return
363
+ }
292
364
  }
293
365
 
294
366
  {{#if isDocument}}
@@ -320,7 +392,11 @@ func (ctrl *{{capitalize name}}Controller) Update(c *gin.Context) {
320
392
  messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "UPDATE", "{{capitalize name}}", entity, nil)
321
393
  {{/if}}
322
394
 
323
- c.JSON(http.StatusOK, entity)
395
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
396
+ c.Render(http.StatusOK, render.MsgPack{Data: entity})
397
+ } else {
398
+ c.JSON(http.StatusOK, entity)
399
+ }
324
400
  }
325
401
 
326
402
  func (ctrl *{{capitalize name}}Controller) Delete(c *gin.Context) {
@@ -356,9 +432,16 @@ func (ctrl *{{capitalize name}}Controller) Patch(c *gin.Context) {
356
432
  ctx := c.Request.Context()
357
433
  id := c.Param("id")
358
434
  var updates map[string]interface{}
359
- if err := c.ShouldBindJSON(&updates); err != nil {
360
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
361
- return
435
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
436
+ if err := c.ShouldBindWith(&updates, binding.MsgPack); err != nil {
437
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
438
+ return
439
+ }
440
+ } else {
441
+ if err := c.ShouldBindJSON(&updates); err != nil {
442
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
443
+ return
444
+ }
362
445
  }
363
446
 
364
447
  {{#if isDocument}}
@@ -383,7 +466,11 @@ func (ctrl *{{capitalize name}}Controller) Patch(c *gin.Context) {
383
466
  messaging.PublishEvent(ctrl.Config.GoDuck.Messaging.MQTT.TopicPrefix, "tenant", "PATCH", "{{capitalize name}}", updates, nil)
384
467
  {{/if}}
385
468
 
386
- c.JSON(http.StatusOK, gin.H{"message": "Patch successful"})
469
+ if ctrl.Config.GoDuck.Server.REST.Protocol == "messagepack" {
470
+ c.Render(http.StatusOK, render.MsgPack{Data: gin.H{"message": "Patch successful"}})
471
+ } else {
472
+ c.JSON(http.StatusOK, gin.H{"message": "Patch successful"})
473
+ }
387
474
  }
388
475
 
389
476
  func (ctrl *{{capitalize name}}Controller) BulkCreate(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{"error": "Bulk operations use primary silo isolation by default."}) }
@@ -14,6 +14,8 @@ import (
14
14
  "{{app_name}}/integrations/wso2"
15
15
  "gorm.io/driver/postgres"
16
16
  "gorm.io/gorm"
17
+ "net/http"
18
+ "github.com/improbable-eng/grpc-web/go/grpcweb"
17
19
  )
18
20
 
19
21
  func main() {
@@ -40,6 +42,18 @@ func main() {
40
42
  go func() {
41
43
  grpcSrv := server.NewGRPCServer(appConfig, repo)
42
44
  logger.Info("Starting Kratos gRPC server on %s", appConfig.GoDuck.Server.GRPC.Addr)
45
+
46
+ if appConfig.GoDuck.Server.GRPC.WebEnabled {
47
+ wrappedGrpc := grpcweb.WrapServer(grpcSrv.Server)
48
+ go func() {
49
+ webPort := fmt.Sprintf(":%d", appConfig.GoDuck.Server.GRPC.WebPort)
50
+ logger.Info("Starting gRPC-Web Proxy on %s", webPort)
51
+ if err := http.ListenAndServe(webPort, wrappedGrpc); err != nil {
52
+ logger.Error("Failed to start gRPC-Web Proxy: %v", err)
53
+ }
54
+ }()
55
+ }
56
+
43
57
  if err := grpcSrv.Start(context.Background()); err != nil {
44
58
  logger.Error("Failed to start Kratos gRPC server: %v", err)
45
59
  }
@@ -56,7 +70,7 @@ func main() {
56
70
  }
57
71
 
58
72
  // 6. Start HTTP Server
59
- port := fmt.Sprintf(":%d", appConfig.GoDuck.Server.Port)
60
- logger.Info("Starting HTTP server on %s", port)
73
+ port := fmt.Sprintf(":%d", appConfig.GoDuck.Server.REST.Port)
74
+ logger.Info("Starting HTTP REST server on %s", port)
61
75
  r.Run(port)
62
76
  }
@@ -209,8 +209,13 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
209
209
  }
210
210
  {{/if}}
211
211
 
212
+ apiPrefix := appConfig.GoDuck.Server.REST.ApiPathPrefix
213
+ if apiPrefix == "" {
214
+ apiPrefix = "/api"
215
+ }
216
+
212
217
  // Open Application APIs (No Auth)
213
- openApi := r.Group("/open/api")
218
+ openApi := r.Group("/open" + apiPrefix)
214
219
  if appConfig.GoDuck.Multitenancy.Enabled {
215
220
  openApi.Use(middleware.PublicTenantMiddleware(masterDB, appConfig))
216
221
  }
@@ -246,7 +251,7 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
246
251
  }
247
252
 
248
253
  // Secured Application APIs
249
- api := r.Group("/api")
254
+ api := r.Group(apiPrefix)
250
255
  api.Use(middleware.JWTMiddleware())
251
256
  api.Use(middleware.TenantMiddleware(masterDB, appConfig))
252
257
  // Log request DB resolution for debugging
@@ -270,7 +275,7 @@ func SetupRouter(appConfig *config.Config) *gin.Engine {
270
275
  api.GET("/metering/history", meteringCtrl.GetHistory)
271
276
 
272
277
  // --- Infrastructure/Confidential Layer ---
273
- admin := r.Group("/api/admin")
278
+ admin := r.Group(apiPrefix + "/admin")
274
279
  admin.Use(middleware.JWTMiddleware())
275
280
  admin.Use(middleware.SuperAdminRoleMiddleware(appConfig))
276
281
  {
@@ -2,6 +2,7 @@ package service
2
2
 
3
3
  import (
4
4
  "context"
5
+ "strings"
5
6
  {{#if needFmt}}
6
7
  "fmt"
7
8
  {{/if}}
@@ -283,6 +284,15 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
283
284
  for role, db := range siloConns {
284
285
  var results []models.{{capitalize name}}
285
286
  opts := options.Find().SetSkip(int64((req.Page - 1) * req.PageSize)).SetLimit(int64(req.PageSize))
287
+ if req.Sort != "" {
288
+ parts := strings.Split(req.Sort, ",")
289
+ field := parts[0]
290
+ order := 1
291
+ if len(parts) > 1 && strings.ToLower(parts[1]) == "desc" {
292
+ order = -1
293
+ }
294
+ opts.SetSort(bson.D{bson.E{Key: field, Value: order}})
295
+ }
286
296
  cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
287
297
  if err == nil {
288
298
  cursor.All(ctx, &results)
@@ -300,6 +310,15 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
300
310
  if ok {
301
311
  var results []models.{{capitalize name}}
302
312
  opts := options.Find().SetSkip(int64((req.Page - 1) * req.PageSize)).SetLimit(int64(req.PageSize))
313
+ if req.Sort != "" {
314
+ parts := strings.Split(req.Sort, ",")
315
+ field := parts[0]
316
+ order := 1
317
+ if len(parts) > 1 && strings.ToLower(parts[1]) == "desc" {
318
+ order = -1
319
+ }
320
+ opts.SetSort(bson.D{bson.E{Key: field, Value: order}})
321
+ }
303
322
  cursor, err := db.Collection("{{toLowerCase name}}s").Find(ctx, bson.M{}, opts)
304
323
  if err == nil {
305
324
  cursor.All(ctx, &results)
@@ -319,7 +338,9 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
319
338
  for role, db := range siloConns {
320
339
  var results []models.{{capitalize name}}
321
340
  query := db.WithContext(ctx).Model(&models.{{capitalize name}}{})
322
-
341
+ if req.Sort != "" {
342
+ query = query.Order(strings.ReplaceAll(req.Sort, ",", " "))
343
+ }
323
344
  var count int64
324
345
  query.Count(&count)
325
346
  total += count
@@ -338,7 +359,9 @@ func (s *{{capitalize name}}Service) List{{capitalize name}}(ctx context.Context
338
359
  if ok {
339
360
  var results []models.{{capitalize name}}
340
361
  query := db.WithContext(ctx).Model(&models.{{capitalize name}}{})
341
-
362
+ if req.Sort != "" {
363
+ query = query.Order(strings.ReplaceAll(req.Sort, ",", " "))
364
+ }
342
365
  var count int64
343
366
  query.Count(&count)
344
367
  total = count
@@ -83,6 +83,7 @@ message List{{capitalize name}}Request {
83
83
  int32 page = 1;
84
84
  int32 page_size = 2;
85
85
  string query = 3;
86
+ string sort = 4;
86
87
  }
87
88
 
88
89
  message List{{capitalize name}}Reply {