feat(api): add pending issue moderation and the issue submitted event
- commit
- bfc173ed3efb28b65810d2ebdd925cba87887477
- author
- brice · · 2026-10-19 06:50 UTC
- committer
- brice · · 2026-10-19 08:28 UTC
- parent
- 3130756
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +856 -109
M gen/ermite/ermiteconnect/event.connect.go +4 -2
| @@ -49,7 +49,8 @@ type EventServiceClient interface { | |||
| 49 | 49 | // for merge requests, merge_request_opened, merge_request_merged, | |
| 50 | 50 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | |
| 51 | 51 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | |
| 52 | - | // issue_closed, issue_reopened and issue_commented. Each one is delivered on the | |
| 52 | + | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to | |
| 53 | + | // owner watchers only). Each one is delivered on the | |
| 53 | 54 | // "repo:<name>" topic of its repository and on "home", subject to the | |
| 54 | 55 | // visibility of the repository. | |
| 55 | 56 | // | |
| @@ -123,7 +124,8 @@ type EventServiceHandler interface { | |||
| 123 | 124 | // for merge requests, merge_request_opened, merge_request_merged, | |
| 124 | 125 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | |
| 125 | 126 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | |
| 126 | - | // issue_closed, issue_reopened and issue_commented. Each one is delivered on the | |
| 127 | + | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to | |
| 128 | + | // owner watchers only). Each one is delivered on the | |
| 127 | 129 | // "repo:<name>" topic of its repository and on "home", subject to the | |
| 128 | 130 | // visibility of the repository. | |
| 129 | 131 | // | |
| @@ -49,7 +49,8 @@ type EventServiceClient interface { | |||||
| 49 | // for merge requests, merge_request_opened, merge_request_merged, | 49 | // for merge requests, merge_request_opened, merge_request_merged, | ||
| 50 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | 50 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | ||
| 51 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | 51 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | ||
| 52 | - | // issue_closed, issue_reopened and issue_commented. Each one is delivered on the | 52 | + | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to |
| 53 | + | // owner watchers only). Each one is delivered on the | |||
| 53 | // "repo:<name>" topic of its repository and on "home", subject to the | 54 | // "repo:<name>" topic of its repository and on "home", subject to the | ||
| 54 | // visibility of the repository. | 55 | // visibility of the repository. | ||
| 55 | // | 56 | // | ||
| @@ -123,7 +124,8 @@ type EventServiceHandler interface { | |||||
| 123 | // for merge requests, merge_request_opened, merge_request_merged, | 124 | // for merge requests, merge_request_opened, merge_request_merged, | ||
| 124 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | 125 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | ||
| 125 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | 126 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | ||
| 126 | - | // issue_closed, issue_reopened and issue_commented. Each one is delivered on the | 127 | + | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to |
| 128 | + | // owner watchers only). Each one is delivered on the | |||
| 127 | // "repo:<name>" topic of its repository and on "home", subject to the | 129 | // "repo:<name>" topic of its repository and on "home", subject to the | ||
| 128 | // visibility of the repository. | 130 | // visibility of the repository. | ||
| 129 | // | 131 | // | ||
M gen/ermite/ermiteconnect/issue.connect.go +105 -6
| @@ -50,6 +50,15 @@ const ( | |||
| 50 | 50 | // IssueServiceReopenIssueProcedure is the fully-qualified name of the IssueService's ReopenIssue | |
| 51 | 51 | // RPC. | |
| 52 | 52 | IssueServiceReopenIssueProcedure = "/ermite.IssueService/ReopenIssue" | |
| 53 | + | // IssueServiceListPendingIssuesProcedure is the fully-qualified name of the IssueService's | |
| 54 | + | // ListPendingIssues RPC. | |
| 55 | + | IssueServiceListPendingIssuesProcedure = "/ermite.IssueService/ListPendingIssues" | |
| 56 | + | // IssueServiceApproveIssueProcedure is the fully-qualified name of the IssueService's ApproveIssue | |
| 57 | + | // RPC. | |
| 58 | + | IssueServiceApproveIssueProcedure = "/ermite.IssueService/ApproveIssue" | |
| 59 | + | // IssueServiceRejectIssueProcedure is the fully-qualified name of the IssueService's RejectIssue | |
| 60 | + | // RPC. | |
| 61 | + | IssueServiceRejectIssueProcedure = "/ermite.IssueService/RejectIssue" | |
| 53 | 62 | ) | |
| 54 | 63 | ||
| 55 | 64 | // IssueServiceClient is a client for the ermite.IssueService service. | |
| @@ -66,6 +75,15 @@ type IssueServiceClient interface { | |||
| 66 | 75 | CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error) | |
| 67 | 76 | // ReopenIssue reopens an issue. Needs the issue:write scope. | |
| 68 | 77 | ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) | |
| 78 | + | // ListPendingIssues lists issues awaiting moderation, oldest first. Owner | |
| 79 | + | // rights only; anyone else gets NOT_FOUND. | |
| 80 | + | ListPendingIssues(context.Context, *connect.Request[ermite.ListPendingIssuesRequest]) (*connect.Response[ermite.ListPendingIssuesResponse], error) | |
| 81 | + | // ApproveIssue turns a pending issue into an open issue with a new number. | |
| 82 | + | // Owner rights only; anyone else gets NOT_FOUND. | |
| 83 | + | ApproveIssue(context.Context, *connect.Request[ermite.ApproveIssueRequest]) (*connect.Response[ermite.ApproveIssueResponse], error) | |
| 84 | + | // RejectIssue discards a pending issue, optionally banning its submitter. | |
| 85 | + | // Owner rights only; anyone else gets NOT_FOUND. | |
| 86 | + | RejectIssue(context.Context, *connect.Request[ermite.RejectIssueRequest]) (*connect.Response[ermite.RejectIssueResponse], error) | |
| 69 | 87 | } | |
| 70 | 88 | ||
| 71 | 89 | // NewIssueServiceClient constructs a client for the ermite.IssueService service. By default, it | |
| @@ -115,17 +133,38 @@ func NewIssueServiceClient(httpClient connect.HTTPClient, baseURL string, opts . | |||
| 115 | 133 | connect.WithSchema(issueServiceMethods.ByName("ReopenIssue")), | |
| 116 | 134 | connect.WithClientOptions(opts...), | |
| 117 | 135 | ), | |
| 136 | + | listPendingIssues: connect.NewClient[ermite.ListPendingIssuesRequest, ermite.ListPendingIssuesResponse]( | |
| 137 | + | httpClient, | |
| 138 | + | baseURL+IssueServiceListPendingIssuesProcedure, | |
| 139 | + | connect.WithSchema(issueServiceMethods.ByName("ListPendingIssues")), | |
| 140 | + | connect.WithClientOptions(opts...), | |
| 141 | + | ), | |
| 142 | + | approveIssue: connect.NewClient[ermite.ApproveIssueRequest, ermite.ApproveIssueResponse]( | |
| 143 | + | httpClient, | |
| 144 | + | baseURL+IssueServiceApproveIssueProcedure, | |
| 145 | + | connect.WithSchema(issueServiceMethods.ByName("ApproveIssue")), | |
| 146 | + | connect.WithClientOptions(opts...), | |
| 147 | + | ), | |
| 148 | + | rejectIssue: connect.NewClient[ermite.RejectIssueRequest, ermite.RejectIssueResponse]( | |
| 149 | + | httpClient, | |
| 150 | + | baseURL+IssueServiceRejectIssueProcedure, | |
| 151 | + | connect.WithSchema(issueServiceMethods.ByName("RejectIssue")), | |
| 152 | + | connect.WithClientOptions(opts...), | |
| 153 | + | ), | |
| 118 | 154 | } | |
| 119 | 155 | } | |
| 120 | 156 | ||
| 121 | 157 | // issueServiceClient implements IssueServiceClient. | |
| 122 | 158 | type issueServiceClient struct { | |
| 123 | - | createIssue *connect.Client[ermite.CreateIssueRequest, ermite.CreateIssueResponse] | |
| 124 | - | listIssues *connect.Client[ermite.ListIssuesRequest, ermite.ListIssuesResponse] | |
| 125 | - | getIssue *connect.Client[ermite.GetIssueRequest, ermite.GetIssueResponse] | |
| 126 | - | addIssueComment *connect.Client[ermite.AddIssueCommentRequest, ermite.AddIssueCommentResponse] | |
| 127 | - | closeIssue *connect.Client[ermite.CloseIssueRequest, ermite.CloseIssueResponse] | |
| 128 | - | reopenIssue *connect.Client[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse] | |
| 159 | + | createIssue *connect.Client[ermite.CreateIssueRequest, ermite.CreateIssueResponse] | |
| 160 | + | listIssues *connect.Client[ermite.ListIssuesRequest, ermite.ListIssuesResponse] | |
| 161 | + | getIssue *connect.Client[ermite.GetIssueRequest, ermite.GetIssueResponse] | |
| 162 | + | addIssueComment *connect.Client[ermite.AddIssueCommentRequest, ermite.AddIssueCommentResponse] | |
| 163 | + | closeIssue *connect.Client[ermite.CloseIssueRequest, ermite.CloseIssueResponse] | |
| 164 | + | reopenIssue *connect.Client[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse] | |
| 165 | + | listPendingIssues *connect.Client[ermite.ListPendingIssuesRequest, ermite.ListPendingIssuesResponse] | |
| 166 | + | approveIssue *connect.Client[ermite.ApproveIssueRequest, ermite.ApproveIssueResponse] | |
| 167 | + | rejectIssue *connect.Client[ermite.RejectIssueRequest, ermite.RejectIssueResponse] | |
| 129 | 168 | } | |
| 130 | 169 | ||
| 131 | 170 | // CreateIssue calls ermite.IssueService.CreateIssue. | |
| @@ -158,6 +197,21 @@ func (c *issueServiceClient) ReopenIssue(ctx context.Context, req *connect.Reque | |||
| 158 | 197 | return c.reopenIssue.CallUnary(ctx, req) | |
| 159 | 198 | } | |
| 160 | 199 | ||
| 200 | + | // ListPendingIssues calls ermite.IssueService.ListPendingIssues. | |
| 201 | + | func (c *issueServiceClient) ListPendingIssues(ctx context.Context, req *connect.Request[ermite.ListPendingIssuesRequest]) (*connect.Response[ermite.ListPendingIssuesResponse], error) { | |
| 202 | + | return c.listPendingIssues.CallUnary(ctx, req) | |
| 203 | + | } | |
| 204 | + | ||
| 205 | + | // ApproveIssue calls ermite.IssueService.ApproveIssue. | |
| 206 | + | func (c *issueServiceClient) ApproveIssue(ctx context.Context, req *connect.Request[ermite.ApproveIssueRequest]) (*connect.Response[ermite.ApproveIssueResponse], error) { | |
| 207 | + | return c.approveIssue.CallUnary(ctx, req) | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | // RejectIssue calls ermite.IssueService.RejectIssue. | |
| 211 | + | func (c *issueServiceClient) RejectIssue(ctx context.Context, req *connect.Request[ermite.RejectIssueRequest]) (*connect.Response[ermite.RejectIssueResponse], error) { | |
| 212 | + | return c.rejectIssue.CallUnary(ctx, req) | |
| 213 | + | } | |
| 214 | + | ||
| 161 | 215 | // IssueServiceHandler is an implementation of the ermite.IssueService service. | |
| 162 | 216 | type IssueServiceHandler interface { | |
| 163 | 217 | // CreateIssue opens an issue. Needs the issue:write scope. | |
| @@ -172,6 +226,15 @@ type IssueServiceHandler interface { | |||
| 172 | 226 | CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error) | |
| 173 | 227 | // ReopenIssue reopens an issue. Needs the issue:write scope. | |
| 174 | 228 | ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) | |
| 229 | + | // ListPendingIssues lists issues awaiting moderation, oldest first. Owner | |
| 230 | + | // rights only; anyone else gets NOT_FOUND. | |
| 231 | + | ListPendingIssues(context.Context, *connect.Request[ermite.ListPendingIssuesRequest]) (*connect.Response[ermite.ListPendingIssuesResponse], error) | |
| 232 | + | // ApproveIssue turns a pending issue into an open issue with a new number. | |
| 233 | + | // Owner rights only; anyone else gets NOT_FOUND. | |
| 234 | + | ApproveIssue(context.Context, *connect.Request[ermite.ApproveIssueRequest]) (*connect.Response[ermite.ApproveIssueResponse], error) | |
| 235 | + | // RejectIssue discards a pending issue, optionally banning its submitter. | |
| 236 | + | // Owner rights only; anyone else gets NOT_FOUND. | |
| 237 | + | RejectIssue(context.Context, *connect.Request[ermite.RejectIssueRequest]) (*connect.Response[ermite.RejectIssueResponse], error) | |
| 175 | 238 | } | |
| 176 | 239 | ||
| 177 | 240 | // NewIssueServiceHandler builds an HTTP handler from the service implementation. It returns the | |
| @@ -217,6 +280,24 @@ func NewIssueServiceHandler(svc IssueServiceHandler, opts ...connect.HandlerOpti | |||
| 217 | 280 | connect.WithSchema(issueServiceMethods.ByName("ReopenIssue")), | |
| 218 | 281 | connect.WithHandlerOptions(opts...), | |
| 219 | 282 | ) | |
| 283 | + | issueServiceListPendingIssuesHandler := connect.NewUnaryHandler( | |
| 284 | + | IssueServiceListPendingIssuesProcedure, | |
| 285 | + | svc.ListPendingIssues, | |
| 286 | + | connect.WithSchema(issueServiceMethods.ByName("ListPendingIssues")), | |
| 287 | + | connect.WithHandlerOptions(opts...), | |
| 288 | + | ) | |
| 289 | + | issueServiceApproveIssueHandler := connect.NewUnaryHandler( | |
| 290 | + | IssueServiceApproveIssueProcedure, | |
| 291 | + | svc.ApproveIssue, | |
| 292 | + | connect.WithSchema(issueServiceMethods.ByName("ApproveIssue")), | |
| 293 | + | connect.WithHandlerOptions(opts...), | |
| 294 | + | ) | |
| 295 | + | issueServiceRejectIssueHandler := connect.NewUnaryHandler( | |
| 296 | + | IssueServiceRejectIssueProcedure, | |
| 297 | + | svc.RejectIssue, | |
| 298 | + | connect.WithSchema(issueServiceMethods.ByName("RejectIssue")), | |
| 299 | + | connect.WithHandlerOptions(opts...), | |
| 300 | + | ) | |
| 220 | 301 | return "/ermite.IssueService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 221 | 302 | switch r.URL.Path { | |
| 222 | 303 | case IssueServiceCreateIssueProcedure: | |
| @@ -231,6 +312,12 @@ func NewIssueServiceHandler(svc IssueServiceHandler, opts ...connect.HandlerOpti | |||
| 231 | 312 | issueServiceCloseIssueHandler.ServeHTTP(w, r) | |
| 232 | 313 | case IssueServiceReopenIssueProcedure: | |
| 233 | 314 | issueServiceReopenIssueHandler.ServeHTTP(w, r) | |
| 315 | + | case IssueServiceListPendingIssuesProcedure: | |
| 316 | + | issueServiceListPendingIssuesHandler.ServeHTTP(w, r) | |
| 317 | + | case IssueServiceApproveIssueProcedure: | |
| 318 | + | issueServiceApproveIssueHandler.ServeHTTP(w, r) | |
| 319 | + | case IssueServiceRejectIssueProcedure: | |
| 320 | + | issueServiceRejectIssueHandler.ServeHTTP(w, r) | |
| 234 | 321 | default: | |
| 235 | 322 | http.NotFound(w, r) | |
| 236 | 323 | } | |
| @@ -263,3 +350,15 @@ func (UnimplementedIssueServiceHandler) CloseIssue(context.Context, *connect.Req | |||
| 263 | 350 | func (UnimplementedIssueServiceHandler) ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) { | |
| 264 | 351 | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ReopenIssue is not implemented")) | |
| 265 | 352 | } | |
| 353 | + | ||
| 354 | + | func (UnimplementedIssueServiceHandler) ListPendingIssues(context.Context, *connect.Request[ermite.ListPendingIssuesRequest]) (*connect.Response[ermite.ListPendingIssuesResponse], error) { | |
| 355 | + | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ListPendingIssues is not implemented")) | |
| 356 | + | } | |
| 357 | + | ||
| 358 | + | func (UnimplementedIssueServiceHandler) ApproveIssue(context.Context, *connect.Request[ermite.ApproveIssueRequest]) (*connect.Response[ermite.ApproveIssueResponse], error) { | |
| 359 | + | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ApproveIssue is not implemented")) | |
| 360 | + | } | |
| 361 | + | ||
| 362 | + | func (UnimplementedIssueServiceHandler) RejectIssue(context.Context, *connect.Request[ermite.RejectIssueRequest]) (*connect.Response[ermite.RejectIssueResponse], error) { | |
| 363 | + | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.RejectIssue is not implemented")) | |
| 364 | + | } | |
| @@ -50,6 +50,15 @@ const ( | |||||
| 50 | // IssueServiceReopenIssueProcedure is the fully-qualified name of the IssueService's ReopenIssue | 50 | // IssueServiceReopenIssueProcedure is the fully-qualified name of the IssueService's ReopenIssue | ||
| 51 | // RPC. | 51 | // RPC. | ||
| 52 | IssueServiceReopenIssueProcedure = "/ermite.IssueService/ReopenIssue" | 52 | IssueServiceReopenIssueProcedure = "/ermite.IssueService/ReopenIssue" | ||
| 53 | + | // IssueServiceListPendingIssuesProcedure is the fully-qualified name of the IssueService's | |||
| 54 | + | // ListPendingIssues RPC. | |||
| 55 | + | IssueServiceListPendingIssuesProcedure = "/ermite.IssueService/ListPendingIssues" | |||
| 56 | + | // IssueServiceApproveIssueProcedure is the fully-qualified name of the IssueService's ApproveIssue | |||
| 57 | + | // RPC. | |||
| 58 | + | IssueServiceApproveIssueProcedure = "/ermite.IssueService/ApproveIssue" | |||
| 59 | + | // IssueServiceRejectIssueProcedure is the fully-qualified name of the IssueService's RejectIssue | |||
| 60 | + | // RPC. | |||
| 61 | + | IssueServiceRejectIssueProcedure = "/ermite.IssueService/RejectIssue" | |||
| 53 | ) | 62 | ) | ||
| 54 | 63 | ||||
| 55 | // IssueServiceClient is a client for the ermite.IssueService service. | 64 | // IssueServiceClient is a client for the ermite.IssueService service. | ||
| @@ -66,6 +75,15 @@ type IssueServiceClient interface { | |||||
| 66 | CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error) | 75 | CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error) | ||
| 67 | // ReopenIssue reopens an issue. Needs the issue:write scope. | 76 | // ReopenIssue reopens an issue. Needs the issue:write scope. | ||
| 68 | ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) | 77 | ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) | ||
| 78 | + | // ListPendingIssues lists issues awaiting moderation, oldest first. Owner | |||
| 79 | + | // rights only; anyone else gets NOT_FOUND. | |||
| 80 | + | ListPendingIssues(context.Context, *connect.Request[ermite.ListPendingIssuesRequest]) (*connect.Response[ermite.ListPendingIssuesResponse], error) | |||
| 81 | + | // ApproveIssue turns a pending issue into an open issue with a new number. | |||
| 82 | + | // Owner rights only; anyone else gets NOT_FOUND. | |||
| 83 | + | ApproveIssue(context.Context, *connect.Request[ermite.ApproveIssueRequest]) (*connect.Response[ermite.ApproveIssueResponse], error) | |||
| 84 | + | // RejectIssue discards a pending issue, optionally banning its submitter. | |||
| 85 | + | // Owner rights only; anyone else gets NOT_FOUND. | |||
| 86 | + | RejectIssue(context.Context, *connect.Request[ermite.RejectIssueRequest]) (*connect.Response[ermite.RejectIssueResponse], error) | |||
| 69 | } | 87 | } | ||
| 70 | 88 | ||||
| 71 | // NewIssueServiceClient constructs a client for the ermite.IssueService service. By default, it | 89 | // NewIssueServiceClient constructs a client for the ermite.IssueService service. By default, it | ||
| @@ -115,17 +133,38 @@ func NewIssueServiceClient(httpClient connect.HTTPClient, baseURL string, opts . | |||||
| 115 | connect.WithSchema(issueServiceMethods.ByName("ReopenIssue")), | 133 | connect.WithSchema(issueServiceMethods.ByName("ReopenIssue")), | ||
| 116 | connect.WithClientOptions(opts...), | 134 | connect.WithClientOptions(opts...), | ||
| 117 | ), | 135 | ), | ||
| 136 | + | listPendingIssues: connect.NewClient[ermite.ListPendingIssuesRequest, ermite.ListPendingIssuesResponse]( | |||
| 137 | + | httpClient, | |||
| 138 | + | baseURL+IssueServiceListPendingIssuesProcedure, | |||
| 139 | + | connect.WithSchema(issueServiceMethods.ByName("ListPendingIssues")), | |||
| 140 | + | connect.WithClientOptions(opts...), | |||
| 141 | + | ), | |||
| 142 | + | approveIssue: connect.NewClient[ermite.ApproveIssueRequest, ermite.ApproveIssueResponse]( | |||
| 143 | + | httpClient, | |||
| 144 | + | baseURL+IssueServiceApproveIssueProcedure, | |||
| 145 | + | connect.WithSchema(issueServiceMethods.ByName("ApproveIssue")), | |||
| 146 | + | connect.WithClientOptions(opts...), | |||
| 147 | + | ), | |||
| 148 | + | rejectIssue: connect.NewClient[ermite.RejectIssueRequest, ermite.RejectIssueResponse]( | |||
| 149 | + | httpClient, | |||
| 150 | + | baseURL+IssueServiceRejectIssueProcedure, | |||
| 151 | + | connect.WithSchema(issueServiceMethods.ByName("RejectIssue")), | |||
| 152 | + | connect.WithClientOptions(opts...), | |||
| 153 | + | ), | |||
| 118 | } | 154 | } | ||
| 119 | } | 155 | } | ||
| 120 | 156 | ||||
| 121 | // issueServiceClient implements IssueServiceClient. | 157 | // issueServiceClient implements IssueServiceClient. | ||
| 122 | type issueServiceClient struct { | 158 | type issueServiceClient struct { | ||
| 123 | - | createIssue *connect.Client[ermite.CreateIssueRequest, ermite.CreateIssueResponse] | 159 | + | createIssue *connect.Client[ermite.CreateIssueRequest, ermite.CreateIssueResponse] |
| 124 | - | listIssues *connect.Client[ermite.ListIssuesRequest, ermite.ListIssuesResponse] | 160 | + | listIssues *connect.Client[ermite.ListIssuesRequest, ermite.ListIssuesResponse] |
| 125 | - | getIssue *connect.Client[ermite.GetIssueRequest, ermite.GetIssueResponse] | 161 | + | getIssue *connect.Client[ermite.GetIssueRequest, ermite.GetIssueResponse] |
| 126 | - | addIssueComment *connect.Client[ermite.AddIssueCommentRequest, ermite.AddIssueCommentResponse] | 162 | + | addIssueComment *connect.Client[ermite.AddIssueCommentRequest, ermite.AddIssueCommentResponse] |
| 127 | - | closeIssue *connect.Client[ermite.CloseIssueRequest, ermite.CloseIssueResponse] | 163 | + | closeIssue *connect.Client[ermite.CloseIssueRequest, ermite.CloseIssueResponse] |
| 128 | - | reopenIssue *connect.Client[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse] | 164 | + | reopenIssue *connect.Client[ermite.ReopenIssueRequest, ermite.ReopenIssueResponse] |
| 165 | + | listPendingIssues *connect.Client[ermite.ListPendingIssuesRequest, ermite.ListPendingIssuesResponse] | |||
| 166 | + | approveIssue *connect.Client[ermite.ApproveIssueRequest, ermite.ApproveIssueResponse] | |||
| 167 | + | rejectIssue *connect.Client[ermite.RejectIssueRequest, ermite.RejectIssueResponse] | |||
| 129 | } | 168 | } | ||
| 130 | 169 | ||||
| 131 | // CreateIssue calls ermite.IssueService.CreateIssue. | 170 | // CreateIssue calls ermite.IssueService.CreateIssue. | ||
| @@ -158,6 +197,21 @@ func (c *issueServiceClient) ReopenIssue(ctx context.Context, req *connect.Reque | |||||
| 158 | return c.reopenIssue.CallUnary(ctx, req) | 197 | return c.reopenIssue.CallUnary(ctx, req) | ||
| 159 | } | 198 | } | ||
| 160 | 199 | ||||
| 200 | + | // ListPendingIssues calls ermite.IssueService.ListPendingIssues. | |||
| 201 | + | func (c *issueServiceClient) ListPendingIssues(ctx context.Context, req *connect.Request[ermite.ListPendingIssuesRequest]) (*connect.Response[ermite.ListPendingIssuesResponse], error) { | |||
| 202 | + | return c.listPendingIssues.CallUnary(ctx, req) | |||
| 203 | + | } | |||
| 204 | + | ||||
| 205 | + | // ApproveIssue calls ermite.IssueService.ApproveIssue. | |||
| 206 | + | func (c *issueServiceClient) ApproveIssue(ctx context.Context, req *connect.Request[ermite.ApproveIssueRequest]) (*connect.Response[ermite.ApproveIssueResponse], error) { | |||
| 207 | + | return c.approveIssue.CallUnary(ctx, req) | |||
| 208 | + | } | |||
| 209 | + | ||||
| 210 | + | // RejectIssue calls ermite.IssueService.RejectIssue. | |||
| 211 | + | func (c *issueServiceClient) RejectIssue(ctx context.Context, req *connect.Request[ermite.RejectIssueRequest]) (*connect.Response[ermite.RejectIssueResponse], error) { | |||
| 212 | + | return c.rejectIssue.CallUnary(ctx, req) | |||
| 213 | + | } | |||
| 214 | + | ||||
| 161 | // IssueServiceHandler is an implementation of the ermite.IssueService service. | 215 | // IssueServiceHandler is an implementation of the ermite.IssueService service. | ||
| 162 | type IssueServiceHandler interface { | 216 | type IssueServiceHandler interface { | ||
| 163 | // CreateIssue opens an issue. Needs the issue:write scope. | 217 | // CreateIssue opens an issue. Needs the issue:write scope. | ||
| @@ -172,6 +226,15 @@ type IssueServiceHandler interface { | |||||
| 172 | CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error) | 226 | CloseIssue(context.Context, *connect.Request[ermite.CloseIssueRequest]) (*connect.Response[ermite.CloseIssueResponse], error) | ||
| 173 | // ReopenIssue reopens an issue. Needs the issue:write scope. | 227 | // ReopenIssue reopens an issue. Needs the issue:write scope. | ||
| 174 | ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) | 228 | ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) | ||
| 229 | + | // ListPendingIssues lists issues awaiting moderation, oldest first. Owner | |||
| 230 | + | // rights only; anyone else gets NOT_FOUND. | |||
| 231 | + | ListPendingIssues(context.Context, *connect.Request[ermite.ListPendingIssuesRequest]) (*connect.Response[ermite.ListPendingIssuesResponse], error) | |||
| 232 | + | // ApproveIssue turns a pending issue into an open issue with a new number. | |||
| 233 | + | // Owner rights only; anyone else gets NOT_FOUND. | |||
| 234 | + | ApproveIssue(context.Context, *connect.Request[ermite.ApproveIssueRequest]) (*connect.Response[ermite.ApproveIssueResponse], error) | |||
| 235 | + | // RejectIssue discards a pending issue, optionally banning its submitter. | |||
| 236 | + | // Owner rights only; anyone else gets NOT_FOUND. | |||
| 237 | + | RejectIssue(context.Context, *connect.Request[ermite.RejectIssueRequest]) (*connect.Response[ermite.RejectIssueResponse], error) | |||
| 175 | } | 238 | } | ||
| 176 | 239 | ||||
| 177 | // NewIssueServiceHandler builds an HTTP handler from the service implementation. It returns the | 240 | // NewIssueServiceHandler builds an HTTP handler from the service implementation. It returns the | ||
| @@ -217,6 +280,24 @@ func NewIssueServiceHandler(svc IssueServiceHandler, opts ...connect.HandlerOpti | |||||
| 217 | connect.WithSchema(issueServiceMethods.ByName("ReopenIssue")), | 280 | connect.WithSchema(issueServiceMethods.ByName("ReopenIssue")), | ||
| 218 | connect.WithHandlerOptions(opts...), | 281 | connect.WithHandlerOptions(opts...), | ||
| 219 | ) | 282 | ) | ||
| 283 | + | issueServiceListPendingIssuesHandler := connect.NewUnaryHandler( | |||
| 284 | + | IssueServiceListPendingIssuesProcedure, | |||
| 285 | + | svc.ListPendingIssues, | |||
| 286 | + | connect.WithSchema(issueServiceMethods.ByName("ListPendingIssues")), | |||
| 287 | + | connect.WithHandlerOptions(opts...), | |||
| 288 | + | ) | |||
| 289 | + | issueServiceApproveIssueHandler := connect.NewUnaryHandler( | |||
| 290 | + | IssueServiceApproveIssueProcedure, | |||
| 291 | + | svc.ApproveIssue, | |||
| 292 | + | connect.WithSchema(issueServiceMethods.ByName("ApproveIssue")), | |||
| 293 | + | connect.WithHandlerOptions(opts...), | |||
| 294 | + | ) | |||
| 295 | + | issueServiceRejectIssueHandler := connect.NewUnaryHandler( | |||
| 296 | + | IssueServiceRejectIssueProcedure, | |||
| 297 | + | svc.RejectIssue, | |||
| 298 | + | connect.WithSchema(issueServiceMethods.ByName("RejectIssue")), | |||
| 299 | + | connect.WithHandlerOptions(opts...), | |||
| 300 | + | ) | |||
| 220 | return "/ermite.IssueService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 301 | return "/ermite.IssueService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| 221 | switch r.URL.Path { | 302 | switch r.URL.Path { | ||
| 222 | case IssueServiceCreateIssueProcedure: | 303 | case IssueServiceCreateIssueProcedure: | ||
| @@ -231,6 +312,12 @@ func NewIssueServiceHandler(svc IssueServiceHandler, opts ...connect.HandlerOpti | |||||
| 231 | issueServiceCloseIssueHandler.ServeHTTP(w, r) | 312 | issueServiceCloseIssueHandler.ServeHTTP(w, r) | ||
| 232 | case IssueServiceReopenIssueProcedure: | 313 | case IssueServiceReopenIssueProcedure: | ||
| 233 | issueServiceReopenIssueHandler.ServeHTTP(w, r) | 314 | issueServiceReopenIssueHandler.ServeHTTP(w, r) | ||
| 315 | + | case IssueServiceListPendingIssuesProcedure: | |||
| 316 | + | issueServiceListPendingIssuesHandler.ServeHTTP(w, r) | |||
| 317 | + | case IssueServiceApproveIssueProcedure: | |||
| 318 | + | issueServiceApproveIssueHandler.ServeHTTP(w, r) | |||
| 319 | + | case IssueServiceRejectIssueProcedure: | |||
| 320 | + | issueServiceRejectIssueHandler.ServeHTTP(w, r) | |||
| 234 | default: | 321 | default: | ||
| 235 | http.NotFound(w, r) | 322 | http.NotFound(w, r) | ||
| 236 | } | 323 | } | ||
| @@ -263,3 +350,15 @@ func (UnimplementedIssueServiceHandler) CloseIssue(context.Context, *connect.Req | |||||
| 263 | func (UnimplementedIssueServiceHandler) ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) { | 350 | func (UnimplementedIssueServiceHandler) ReopenIssue(context.Context, *connect.Request[ermite.ReopenIssueRequest]) (*connect.Response[ermite.ReopenIssueResponse], error) { | ||
| 264 | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ReopenIssue is not implemented")) | 351 | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ReopenIssue is not implemented")) | ||
| 265 | } | 352 | } | ||
| 353 | + | ||||
| 354 | + | func (UnimplementedIssueServiceHandler) ListPendingIssues(context.Context, *connect.Request[ermite.ListPendingIssuesRequest]) (*connect.Response[ermite.ListPendingIssuesResponse], error) { | |||
| 355 | + | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ListPendingIssues is not implemented")) | |||
| 356 | + | } | |||
| 357 | + | ||||
| 358 | + | func (UnimplementedIssueServiceHandler) ApproveIssue(context.Context, *connect.Request[ermite.ApproveIssueRequest]) (*connect.Response[ermite.ApproveIssueResponse], error) { | |||
| 359 | + | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.ApproveIssue is not implemented")) | |||
| 360 | + | } | |||
| 361 | + | ||||
| 362 | + | func (UnimplementedIssueServiceHandler) RejectIssue(context.Context, *connect.Request[ermite.RejectIssueRequest]) (*connect.Response[ermite.RejectIssueResponse], error) { | |||
| 363 | + | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.IssueService.RejectIssue is not implemented")) | |||
| 364 | + | } | |||
M gen/ermite/event.pb.go +134 -52 generated file: collapsed
M gen/ermite/issue.pb.go +503 -41 generated file: collapsed
M issue_test.go +20 -7
| @@ -13,12 +13,15 @@ import ( | |||
| 13 | 13 | ||
| 14 | 14 | func TestIssueProcedures(t *testing.T) { | |
| 15 | 15 | tests := map[string]string{ | |
| 16 | - | ermiteconnect.IssueServiceCreateIssueProcedure: "CreateIssue", | |
| 17 | - | ermiteconnect.IssueServiceListIssuesProcedure: "ListIssues", | |
| 18 | - | ermiteconnect.IssueServiceGetIssueProcedure: "GetIssue", | |
| 19 | - | ermiteconnect.IssueServiceAddIssueCommentProcedure: "AddIssueComment", | |
| 20 | - | ermiteconnect.IssueServiceCloseIssueProcedure: "CloseIssue", | |
| 21 | - | ermiteconnect.IssueServiceReopenIssueProcedure: "ReopenIssue", | |
| 16 | + | ermiteconnect.IssueServiceCreateIssueProcedure: "CreateIssue", | |
| 17 | + | ermiteconnect.IssueServiceListIssuesProcedure: "ListIssues", | |
| 18 | + | ermiteconnect.IssueServiceGetIssueProcedure: "GetIssue", | |
| 19 | + | ermiteconnect.IssueServiceAddIssueCommentProcedure: "AddIssueComment", | |
| 20 | + | ermiteconnect.IssueServiceCloseIssueProcedure: "CloseIssue", | |
| 21 | + | ermiteconnect.IssueServiceReopenIssueProcedure: "ReopenIssue", | |
| 22 | + | ermiteconnect.IssueServiceListPendingIssuesProcedure: "ListPendingIssues", | |
| 23 | + | ermiteconnect.IssueServiceApproveIssueProcedure: "ApproveIssue", | |
| 24 | + | ermiteconnect.IssueServiceRejectIssueProcedure: "RejectIssue", | |
| 22 | 25 | } | |
| 23 | 26 | for got, name := range tests { | |
| 24 | 27 | if want := "/ermite.IssueService/" + name; got != want { | |
| @@ -36,7 +39,14 @@ func TestIssueContract(t *testing.T) { | |||
| 36 | 39 | {&ermite.IssueComment{}, map[string]int{"id": 1, "author": 2, "text": 3, "created_at": 4}}, | |
| 37 | 40 | {&ermite.RepoSettings{}, map[string]int{"issues": 3}}, | |
| 38 | 41 | {&ermite.UpdateRepoRequest{}, map[string]int{"issues": 8}}, | |
| 39 | - | {&ermite.Event{}, map[string]int{"issue_opened": 17, "issue_closed": 18, "issue_reopened": 19, "issue_commented": 20}}, | |
| 42 | + | {&ermite.Event{}, map[string]int{"issue_opened": 17, "issue_closed": 18, "issue_reopened": 19, "issue_commented": 20, "issue_submitted": 21}}, | |
| 43 | + | {&ermite.IssueSubmitted{}, map[string]int{"repo": 1, "id": 2}}, | |
| 44 | + | {&ermite.PendingIssue{}, map[string]int{"id": 1, "repo": 2, "title": 3, "body": 4, "author": 5, "created_at": 6}}, | |
| 45 | + | {&ermite.ListPendingIssuesRequest{}, map[string]int{"repo": 1, "page_size": 2, "page_token": 3}}, | |
| 46 | + | {&ermite.ListPendingIssuesResponse{}, map[string]int{"issues": 1, "next_page_token": 2}}, | |
| 47 | + | {&ermite.ApproveIssueRequest{}, map[string]int{"repo": 1, "id": 2}}, | |
| 48 | + | {&ermite.ApproveIssueResponse{}, map[string]int{"issue": 1}}, | |
| 49 | + | {&ermite.RejectIssueRequest{}, map[string]int{"repo": 1, "id": 2, "ban": 3}}, | |
| 40 | 50 | } { | |
| 41 | 51 | md := tt.msg.ProtoReflect().Descriptor() | |
| 42 | 52 | for name, num := range tt.fields { | |
| @@ -64,6 +74,9 @@ func TestIssueContract(t *testing.T) { | |||
| 64 | 74 | &ermite.Event{Kind: &ermite.Event_IssueOpened{IssueOpened: &ermite.IssueOpened{Issue: &ermite.Issue{Repo: "r", Number: 1}}}}, | |
| 65 | 75 | &ermite.Event{Kind: &ermite.Event_IssueClosed{IssueClosed: &ermite.IssueClosed{Issue: &ermite.Issue{Repo: "r", Number: 1}}}}, | |
| 66 | 76 | &ermite.Event{Kind: &ermite.Event_IssueReopened{IssueReopened: &ermite.IssueReopened{Issue: &ermite.Issue{Repo: "r", Number: 1}}}}, | |
| 77 | + | &ermite.Event{Kind: &ermite.Event_IssueSubmitted{IssueSubmitted: &ermite.IssueSubmitted{Repo: "r", Id: "p1"}}}, | |
| 78 | + | &ermite.PendingIssue{Id: "p1", Repo: "r", Title: "t", Author: "anon"}, | |
| 79 | + | &ermite.RejectIssueRequest{Repo: "r", Id: "p1", Ban: true}, | |
| 67 | 80 | &ermite.Event{Kind: &ermite.Event_IssueCommented{IssueCommented: &ermite.IssueCommented{Repo: "r", Number: 1, Comment: &ermite.IssueComment{Id: "c", Text: "hi"}}}}, | |
| 68 | 81 | } { | |
| 69 | 82 | b, err := proto.Marshal(in) | |
| @@ -13,12 +13,15 @@ import ( | |||||
| 13 | 13 | ||||
| 14 | func TestIssueProcedures(t *testing.T) { | 14 | func TestIssueProcedures(t *testing.T) { | ||
| 15 | tests := map[string]string{ | 15 | tests := map[string]string{ | ||
| 16 | - | ermiteconnect.IssueServiceCreateIssueProcedure: "CreateIssue", | 16 | + | ermiteconnect.IssueServiceCreateIssueProcedure: "CreateIssue", |
| 17 | - | ermiteconnect.IssueServiceListIssuesProcedure: "ListIssues", | 17 | + | ermiteconnect.IssueServiceListIssuesProcedure: "ListIssues", |
| 18 | - | ermiteconnect.IssueServiceGetIssueProcedure: "GetIssue", | 18 | + | ermiteconnect.IssueServiceGetIssueProcedure: "GetIssue", |
| 19 | - | ermiteconnect.IssueServiceAddIssueCommentProcedure: "AddIssueComment", | 19 | + | ermiteconnect.IssueServiceAddIssueCommentProcedure: "AddIssueComment", |
| 20 | - | ermiteconnect.IssueServiceCloseIssueProcedure: "CloseIssue", | 20 | + | ermiteconnect.IssueServiceCloseIssueProcedure: "CloseIssue", |
| 21 | - | ermiteconnect.IssueServiceReopenIssueProcedure: "ReopenIssue", | 21 | + | ermiteconnect.IssueServiceReopenIssueProcedure: "ReopenIssue", |
| 22 | + | ermiteconnect.IssueServiceListPendingIssuesProcedure: "ListPendingIssues", | |||
| 23 | + | ermiteconnect.IssueServiceApproveIssueProcedure: "ApproveIssue", | |||
| 24 | + | ermiteconnect.IssueServiceRejectIssueProcedure: "RejectIssue", | |||
| 22 | } | 25 | } | ||
| 23 | for got, name := range tests { | 26 | for got, name := range tests { | ||
| 24 | if want := "/ermite.IssueService/" + name; got != want { | 27 | if want := "/ermite.IssueService/" + name; got != want { | ||
| @@ -36,7 +39,14 @@ func TestIssueContract(t *testing.T) { | |||||
| 36 | {&ermite.IssueComment{}, map[string]int{"id": 1, "author": 2, "text": 3, "created_at": 4}}, | 39 | {&ermite.IssueComment{}, map[string]int{"id": 1, "author": 2, "text": 3, "created_at": 4}}, | ||
| 37 | {&ermite.RepoSettings{}, map[string]int{"issues": 3}}, | 40 | {&ermite.RepoSettings{}, map[string]int{"issues": 3}}, | ||
| 38 | {&ermite.UpdateRepoRequest{}, map[string]int{"issues": 8}}, | 41 | {&ermite.UpdateRepoRequest{}, map[string]int{"issues": 8}}, | ||
| 39 | - | {&ermite.Event{}, map[string]int{"issue_opened": 17, "issue_closed": 18, "issue_reopened": 19, "issue_commented": 20}}, | 42 | + | {&ermite.Event{}, map[string]int{"issue_opened": 17, "issue_closed": 18, "issue_reopened": 19, "issue_commented": 20, "issue_submitted": 21}}, |
| 43 | + | {&ermite.IssueSubmitted{}, map[string]int{"repo": 1, "id": 2}}, | |||
| 44 | + | {&ermite.PendingIssue{}, map[string]int{"id": 1, "repo": 2, "title": 3, "body": 4, "author": 5, "created_at": 6}}, | |||
| 45 | + | {&ermite.ListPendingIssuesRequest{}, map[string]int{"repo": 1, "page_size": 2, "page_token": 3}}, | |||
| 46 | + | {&ermite.ListPendingIssuesResponse{}, map[string]int{"issues": 1, "next_page_token": 2}}, | |||
| 47 | + | {&ermite.ApproveIssueRequest{}, map[string]int{"repo": 1, "id": 2}}, | |||
| 48 | + | {&ermite.ApproveIssueResponse{}, map[string]int{"issue": 1}}, | |||
| 49 | + | {&ermite.RejectIssueRequest{}, map[string]int{"repo": 1, "id": 2, "ban": 3}}, | |||
| 40 | } { | 50 | } { | ||
| 41 | md := tt.msg.ProtoReflect().Descriptor() | 51 | md := tt.msg.ProtoReflect().Descriptor() | ||
| 42 | for name, num := range tt.fields { | 52 | for name, num := range tt.fields { | ||
| @@ -64,6 +74,9 @@ func TestIssueContract(t *testing.T) { | |||||
| 64 | &ermite.Event{Kind: &ermite.Event_IssueOpened{IssueOpened: &ermite.IssueOpened{Issue: &ermite.Issue{Repo: "r", Number: 1}}}}, | 74 | &ermite.Event{Kind: &ermite.Event_IssueOpened{IssueOpened: &ermite.IssueOpened{Issue: &ermite.Issue{Repo: "r", Number: 1}}}}, | ||
| 65 | &ermite.Event{Kind: &ermite.Event_IssueClosed{IssueClosed: &ermite.IssueClosed{Issue: &ermite.Issue{Repo: "r", Number: 1}}}}, | 75 | &ermite.Event{Kind: &ermite.Event_IssueClosed{IssueClosed: &ermite.IssueClosed{Issue: &ermite.Issue{Repo: "r", Number: 1}}}}, | ||
| 66 | &ermite.Event{Kind: &ermite.Event_IssueReopened{IssueReopened: &ermite.IssueReopened{Issue: &ermite.Issue{Repo: "r", Number: 1}}}}, | 76 | &ermite.Event{Kind: &ermite.Event_IssueReopened{IssueReopened: &ermite.IssueReopened{Issue: &ermite.Issue{Repo: "r", Number: 1}}}}, | ||
| 77 | + | &ermite.Event{Kind: &ermite.Event_IssueSubmitted{IssueSubmitted: &ermite.IssueSubmitted{Repo: "r", Id: "p1"}}}, | |||
| 78 | + | &ermite.PendingIssue{Id: "p1", Repo: "r", Title: "t", Author: "anon"}, | |||
| 79 | + | &ermite.RejectIssueRequest{Repo: "r", Id: "p1", Ban: true}, | |||
| 67 | &ermite.Event{Kind: &ermite.Event_IssueCommented{IssueCommented: &ermite.IssueCommented{Repo: "r", Number: 1, Comment: &ermite.IssueComment{Id: "c", Text: "hi"}}}}, | 80 | &ermite.Event{Kind: &ermite.Event_IssueCommented{IssueCommented: &ermite.IssueCommented{Repo: "r", Number: 1, Comment: &ermite.IssueComment{Id: "c", Text: "hi"}}}}, | ||
| 68 | } { | 81 | } { | ||
| 69 | b, err := proto.Marshal(in) | 82 | b, err := proto.Marshal(in) | ||
M proto/ermite/event.proto +15 -1
| @@ -20,7 +20,8 @@ service EventService { | |||
| 20 | 20 | // for merge requests, merge_request_opened, merge_request_merged, | |
| 21 | 21 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | |
| 22 | 22 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | |
| 23 | - | // issue_closed, issue_reopened and issue_commented. Each one is delivered on the | |
| 23 | + | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to | |
| 24 | + | // owner watchers only). Each one is delivered on the | |
| 24 | 25 | // "repo:<name>" topic of its repository and on "home", subject to the | |
| 25 | 26 | // visibility of the repository. | |
| 26 | 27 | // | |
| @@ -119,6 +120,9 @@ message Event { | |||
| 119 | 120 | IssueReopened issue_reopened = 19; | |
| 120 | 121 | // IssueCommented: a comment was added to an issue. | |
| 121 | 122 | IssueCommented issue_commented = 20; | |
| 123 | + | // IssueSubmitted: an issue is awaiting moderation. Delivered to owner | |
| 124 | + | // watchers only. | |
| 125 | + | IssueSubmitted issue_submitted = 21; | |
| 122 | 126 | } | |
| 123 | 127 | } | |
| 124 | 128 | ||
| @@ -247,6 +251,16 @@ message IssueCommented { | |||
| 247 | 251 | IssueComment comment = 3; | |
| 248 | 252 | } | |
| 249 | 253 | ||
| 254 | + | // IssueSubmitted reports an issue awaiting moderation. It is delivered to | |
| 255 | + | // owner watchers only. It carries no title or body: content never travels in | |
| 256 | + | // events, read it with IssueService.ListPendingIssues. | |
| 257 | + | message IssueSubmitted { | |
| 258 | + | // Repo is the name of the repository. | |
| 259 | + | string repo = 1; | |
| 260 | + | // Id is the PendingIssue.id. | |
| 261 | + | string id = 2; | |
| 262 | + | } | |
| 263 | + | ||
| 250 | 264 | // Gap tells the client that events were lost: the requested resume point is | |
| 251 | 265 | // older than the server's buffer. It has no fields. The client should reload | |
| 252 | 266 | // the state it displays (repositories, refs, merge requests and their | |
| @@ -20,7 +20,8 @@ service EventService { | |||||
| 20 | // for merge requests, merge_request_opened, merge_request_merged, | 20 | // for merge requests, merge_request_opened, merge_request_merged, | ||
| 21 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | 21 | // merge_request_closed, comment_added, comment_edited, comment_deleted and | ||
| 22 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | 22 | // comment_resolved (a thread resolved or reopened); for issues, issue_opened, | ||
| 23 | - | // issue_closed, issue_reopened and issue_commented. Each one is delivered on the | 23 | + | // issue_closed, issue_reopened and issue_commented (and issue_submitted, to |
| 24 | + | // owner watchers only). Each one is delivered on the | |||
| 24 | // "repo:<name>" topic of its repository and on "home", subject to the | 25 | // "repo:<name>" topic of its repository and on "home", subject to the | ||
| 25 | // visibility of the repository. | 26 | // visibility of the repository. | ||
| 26 | // | 27 | // | ||
| @@ -119,6 +120,9 @@ message Event { | |||||
| 119 | IssueReopened issue_reopened = 19; | 120 | IssueReopened issue_reopened = 19; | ||
| 120 | // IssueCommented: a comment was added to an issue. | 121 | // IssueCommented: a comment was added to an issue. | ||
| 121 | IssueCommented issue_commented = 20; | 122 | IssueCommented issue_commented = 20; | ||
| 123 | + | // IssueSubmitted: an issue is awaiting moderation. Delivered to owner | |||
| 124 | + | // watchers only. | |||
| 125 | + | IssueSubmitted issue_submitted = 21; | |||
| 122 | } | 126 | } | ||
| 123 | } | 127 | } | ||
| 124 | 128 | ||||
| @@ -247,6 +251,16 @@ message IssueCommented { | |||||
| 247 | IssueComment comment = 3; | 251 | IssueComment comment = 3; | ||
| 248 | } | 252 | } | ||
| 249 | 253 | ||||
| 254 | + | // IssueSubmitted reports an issue awaiting moderation. It is delivered to | |||
| 255 | + | // owner watchers only. It carries no title or body: content never travels in | |||
| 256 | + | // events, read it with IssueService.ListPendingIssues. | |||
| 257 | + | message IssueSubmitted { | |||
| 258 | + | // Repo is the name of the repository. | |||
| 259 | + | string repo = 1; | |||
| 260 | + | // Id is the PendingIssue.id. | |||
| 261 | + | string id = 2; | |||
| 262 | + | } | |||
| 263 | + | ||||
| 250 | // Gap tells the client that events were lost: the requested resume point is | 264 | // Gap tells the client that events were lost: the requested resume point is | ||
| 251 | // older than the server's buffer. It has no fields. The client should reload | 265 | // older than the server's buffer. It has no fields. The client should reload | ||
| 252 | // the state it displays (repositories, refs, merge requests and their | 266 | // the state it displays (repositories, refs, merge requests and their | ||
M proto/ermite/issue.proto +75 -0
| @@ -69,6 +69,15 @@ service IssueService { | |||
| 69 | 69 | rpc CloseIssue(CloseIssueRequest) returns (CloseIssueResponse); | |
| 70 | 70 | // ReopenIssue reopens an issue. Needs the issue:write scope. | |
| 71 | 71 | rpc ReopenIssue(ReopenIssueRequest) returns (ReopenIssueResponse); | |
| 72 | + | // ListPendingIssues lists issues awaiting moderation, oldest first. Owner | |
| 73 | + | // rights only; anyone else gets NOT_FOUND. | |
| 74 | + | rpc ListPendingIssues(ListPendingIssuesRequest) returns (ListPendingIssuesResponse); | |
| 75 | + | // ApproveIssue turns a pending issue into an open issue with a new number. | |
| 76 | + | // Owner rights only; anyone else gets NOT_FOUND. | |
| 77 | + | rpc ApproveIssue(ApproveIssueRequest) returns (ApproveIssueResponse); | |
| 78 | + | // RejectIssue discards a pending issue, optionally banning its submitter. | |
| 79 | + | // Owner rights only; anyone else gets NOT_FOUND. | |
| 80 | + | rpc RejectIssue(RejectIssueRequest) returns (RejectIssueResponse); | |
| 72 | 81 | } | |
| 73 | 82 | ||
| 74 | 83 | // CreateIssueRequest describes the issue to open. | |
| @@ -170,3 +179,69 @@ message ReopenIssueResponse { | |||
| 170 | 179 | // Issue is the reopened issue. | |
| 171 | 180 | Issue issue = 1; | |
| 172 | 181 | } | |
| 182 | + | ||
| 183 | + | // PendingIssue is an issue awaiting moderation. It has no number yet. | |
| 184 | + | message PendingIssue { | |
| 185 | + | // Id identifies the pending issue. Opaque internal id, not a number. | |
| 186 | + | string id = 1; | |
| 187 | + | // Repo is the name of the repository. | |
| 188 | + | string repo = 2; | |
| 189 | + | // Title is the one-line title. | |
| 190 | + | string title = 3; | |
| 191 | + | // Body is the content. It may be empty. | |
| 192 | + | string body = 4; | |
| 193 | + | // Author is the optional display name given by the anonymous submitter. It | |
| 194 | + | // may be empty and is not verified. | |
| 195 | + | string author = 5; | |
| 196 | + | // CreatedAt is when the issue was submitted. | |
| 197 | + | google.protobuf.Timestamp created_at = 6; | |
| 198 | + | } | |
| 199 | + | ||
| 200 | + | // ListPendingIssuesRequest selects a page of pending issues. | |
| 201 | + | message ListPendingIssuesRequest { | |
| 202 | + | // Repo is the repository name. Empty means every repository. | |
| 203 | + | string repo = 1; | |
| 204 | + | // PageSize is the maximum number of issues to return. 0 means the server | |
| 205 | + | // default; values above the server maximum are lowered to it. | |
| 206 | + | int32 page_size = 2; | |
| 207 | + | // PageToken is the next_page_token of a previous response, to continue the | |
| 208 | + | // same listing. Empty for the first page. Other fields must be unchanged. | |
| 209 | + | string page_token = 3; | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | // ListPendingIssuesResponse is one page of pending issues. | |
| 213 | + | message ListPendingIssuesResponse { | |
| 214 | + | // Issues are the pending issues of this page, oldest first. | |
| 215 | + | repeated PendingIssue issues = 1; | |
| 216 | + | // NextPageToken is passed as page_token to get the next page. Empty when | |
| 217 | + | // there is no next page. | |
| 218 | + | string next_page_token = 2; | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | // ApproveIssueRequest names the pending issue to approve. | |
| 222 | + | message ApproveIssueRequest { | |
| 223 | + | // Repo is the repository name. | |
| 224 | + | string repo = 1; | |
| 225 | + | // Id is the PendingIssue.id. | |
| 226 | + | string id = 2; | |
| 227 | + | } | |
| 228 | + | ||
| 229 | + | // ApproveIssueResponse holds the approved issue. | |
| 230 | + | message ApproveIssueResponse { | |
| 231 | + | // Issue is the approved issue, with its new number and state | |
| 232 | + | // ISSUE_STATE_OPEN. | |
| 233 | + | Issue issue = 1; | |
| 234 | + | } | |
| 235 | + | ||
| 236 | + | // RejectIssueRequest names the pending issue to reject. | |
| 237 | + | message RejectIssueRequest { | |
| 238 | + | // Repo is the repository name. | |
| 239 | + | string repo = 1; | |
| 240 | + | // Id is the PendingIssue.id. | |
| 241 | + | string id = 2; | |
| 242 | + | // Ban also bans the submitter for a while. | |
| 243 | + | bool ban = 3; | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | // RejectIssueResponse is empty: the pending issue is gone. | |
| 247 | + | message RejectIssueResponse {} | |
| @@ -69,6 +69,15 @@ service IssueService { | |||||
| 69 | rpc CloseIssue(CloseIssueRequest) returns (CloseIssueResponse); | 69 | rpc CloseIssue(CloseIssueRequest) returns (CloseIssueResponse); | ||
| 70 | // ReopenIssue reopens an issue. Needs the issue:write scope. | 70 | // ReopenIssue reopens an issue. Needs the issue:write scope. | ||
| 71 | rpc ReopenIssue(ReopenIssueRequest) returns (ReopenIssueResponse); | 71 | rpc ReopenIssue(ReopenIssueRequest) returns (ReopenIssueResponse); | ||
| 72 | + | // ListPendingIssues lists issues awaiting moderation, oldest first. Owner | |||
| 73 | + | // rights only; anyone else gets NOT_FOUND. | |||
| 74 | + | rpc ListPendingIssues(ListPendingIssuesRequest) returns (ListPendingIssuesResponse); | |||
| 75 | + | // ApproveIssue turns a pending issue into an open issue with a new number. | |||
| 76 | + | // Owner rights only; anyone else gets NOT_FOUND. | |||
| 77 | + | rpc ApproveIssue(ApproveIssueRequest) returns (ApproveIssueResponse); | |||
| 78 | + | // RejectIssue discards a pending issue, optionally banning its submitter. | |||
| 79 | + | // Owner rights only; anyone else gets NOT_FOUND. | |||
| 80 | + | rpc RejectIssue(RejectIssueRequest) returns (RejectIssueResponse); | |||
| 72 | } | 81 | } | ||
| 73 | 82 | ||||
| 74 | // CreateIssueRequest describes the issue to open. | 83 | // CreateIssueRequest describes the issue to open. | ||
| @@ -170,3 +179,69 @@ message ReopenIssueResponse { | |||||
| 170 | // Issue is the reopened issue. | 179 | // Issue is the reopened issue. | ||
| 171 | Issue issue = 1; | 180 | Issue issue = 1; | ||
| 172 | } | 181 | } | ||
| 182 | + | ||||
| 183 | + | // PendingIssue is an issue awaiting moderation. It has no number yet. | |||
| 184 | + | message PendingIssue { | |||
| 185 | + | // Id identifies the pending issue. Opaque internal id, not a number. | |||
| 186 | + | string id = 1; | |||
| 187 | + | // Repo is the name of the repository. | |||
| 188 | + | string repo = 2; | |||
| 189 | + | // Title is the one-line title. | |||
| 190 | + | string title = 3; | |||
| 191 | + | // Body is the content. It may be empty. | |||
| 192 | + | string body = 4; | |||
| 193 | + | // Author is the optional display name given by the anonymous submitter. It | |||
| 194 | + | // may be empty and is not verified. | |||
| 195 | + | string author = 5; | |||
| 196 | + | // CreatedAt is when the issue was submitted. | |||
| 197 | + | google.protobuf.Timestamp created_at = 6; | |||
| 198 | + | } | |||
| 199 | + | ||||
| 200 | + | // ListPendingIssuesRequest selects a page of pending issues. | |||
| 201 | + | message ListPendingIssuesRequest { | |||
| 202 | + | // Repo is the repository name. Empty means every repository. | |||
| 203 | + | string repo = 1; | |||
| 204 | + | // PageSize is the maximum number of issues to return. 0 means the server | |||
| 205 | + | // default; values above the server maximum are lowered to it. | |||
| 206 | + | int32 page_size = 2; | |||
| 207 | + | // PageToken is the next_page_token of a previous response, to continue the | |||
| 208 | + | // same listing. Empty for the first page. Other fields must be unchanged. | |||
| 209 | + | string page_token = 3; | |||
| 210 | + | } | |||
| 211 | + | ||||
| 212 | + | // ListPendingIssuesResponse is one page of pending issues. | |||
| 213 | + | message ListPendingIssuesResponse { | |||
| 214 | + | // Issues are the pending issues of this page, oldest first. | |||
| 215 | + | repeated PendingIssue issues = 1; | |||
| 216 | + | // NextPageToken is passed as page_token to get the next page. Empty when | |||
| 217 | + | // there is no next page. | |||
| 218 | + | string next_page_token = 2; | |||
| 219 | + | } | |||
| 220 | + | ||||
| 221 | + | // ApproveIssueRequest names the pending issue to approve. | |||
| 222 | + | message ApproveIssueRequest { | |||
| 223 | + | // Repo is the repository name. | |||
| 224 | + | string repo = 1; | |||
| 225 | + | // Id is the PendingIssue.id. | |||
| 226 | + | string id = 2; | |||
| 227 | + | } | |||
| 228 | + | ||||
| 229 | + | // ApproveIssueResponse holds the approved issue. | |||
| 230 | + | message ApproveIssueResponse { | |||
| 231 | + | // Issue is the approved issue, with its new number and state | |||
| 232 | + | // ISSUE_STATE_OPEN. | |||
| 233 | + | Issue issue = 1; | |||
| 234 | + | } | |||
| 235 | + | ||||
| 236 | + | // RejectIssueRequest names the pending issue to reject. | |||
| 237 | + | message RejectIssueRequest { | |||
| 238 | + | // Repo is the repository name. | |||
| 239 | + | string repo = 1; | |||
| 240 | + | // Id is the PendingIssue.id. | |||
| 241 | + | string id = 2; | |||
| 242 | + | // Ban also bans the submitter for a while. | |||
| 243 | + | bool ban = 3; | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | // RejectIssueResponse is empty: the pending issue is gone. | |||
| 247 | + | message RejectIssueResponse {} | |||