Unverified Commit 24fc8ab8 authored by memoclaw's avatar memoclaw Committed by GitHub

feat(mentions): add memo mention parsing, notifications, and rendering (#5811)

Co-authored-by: 's avatarmemoclaw <265580040+memoclaw@users.noreply.github.com>
parent 38fc22b7
## Background & Context
Memos stores memo bodies as markdown, rebuilds derived memo metadata into `MemoPayload`, exposes user notifications through the inbox model, and renders memo content in the React client with custom markdown plugins. The requested `@someone` feature spans both top-level memos and memo comments: users need to type `@`, pick a valid person, render the mention inline, and notify the mentioned user. The current product already has adjacent primitives for this work: a backend markdown extension for `#tag`, an inbox-backed notification center, a generic editor suggestion popup, public user profiles under username-based routes, and a memo update path that already rebuilds payloads on create and edit.
External product behavior is consistent on the core interaction but different on scope. Notion supports real-time `@` suggestions inside pages, comments, and discussions, stores mention notifications in an inbox, and suppresses notification if the mentioned user cannot access the content. Confluence supports autocomplete mentions for people and teams, sends a notification on the first mention, and does not keep notifying on repeated mentions in the same page. Coda supports `@` mentions inside comment threads, treats mentions and thread participation as notification triggers, and allows broader comment-subscription settings beyond explicit mentions. These patterns suggest that the common baseline for Memos is inline autocomplete, access-aware notification, deduplication, and a clear separation between mention notifications and broader thread-subscription features.
## Issue Statement
Memos does not currently recognize `@username` tokens as structured content in memo bodies or comment bodies, does not expose any non-admin user-search endpoint that the editor can use to suggest mentionable users, does not persist or diff mention metadata during memo create or update flows, and does not have an inbox or API notification type for mentions. As a result, `@someone` currently behaves as plain text and cannot drive inline rendering, target validation, or notification delivery.
## Current State
- `server/router/api/v1/memo_service.go:32-159` creates memos by copying raw `request.Memo.Content` into `store.Memo`, enforcing length limits, and calling `memopayload.RebuildMemoPayload`; `server/router/api/v1/memo_service.go:404-510` rebuilds payload only when `content` changes during memo updates.
- `server/router/api/v1/memo_service.go:590-681` creates memo comments by internally creating another memo and only generates inbox notifications for non-private comments to the parent memo creator via `InboxMessage_MEMO_COMMENT`.
- `server/router/api/v1/memo_update_helpers.go:27-77` only dispatches webhook and SSE side effects after memo updates; there is no mention-diff side-effect hook.
- `internal/markdown/markdown.go:20-24` defines extracted markdown metadata as `Tags` plus `Property`; `internal/markdown/markdown.go:68-89` only wires the custom tag extension; `internal/markdown/markdown.go:324-386` extracts tags and properties but no mention metadata.
- `internal/markdown/extensions/tag.go:13-23` and the related tag parser/AST types are the only custom inline markdown extension path today.
- `proto/store/memo.proto:7-29` limits `MemoPayload` to `property`, `location`, and `tags`; there is no repeated mention field or structured mention metadata.
- `proto/store/inbox.proto:7-24` defines only `InboxMessage_MEMO_COMMENT`; `proto/api/v1/user_service.proto:592-679` defines only `UserNotification_MEMO_COMMENT`.
- `server/router/api/v1/user_service.go:1272-1312` lists notifications by filtering inbox rows to `InboxMessage_MEMO_COMMENT` only; `server/router/api/v1/user_service.go:1433-1524` converts only that message type into API notifications.
- `web/src/pages/Inboxes.tsx:19-114` and `web/src/components/Inbox/MemoCommentMessage.tsx` only render memo comment notifications; other notification types would currently be dropped.
- `server/router/api/v1/user_service.go:32-70` exposes `ListUsers` only to admins, and `store/user.go:59-74` plus `store/db/sqlite/user.go:88-175` support exact-match user filtering but no general search, ranking, or pagination for mention autocomplete.
- `server/router/api/v1/acl_config.go:20-27` whitelists `/memos.api.v1.UserService/SearchUsers`, but `proto/api/v1/user_service.proto:16-120` does not define a `SearchUsers` RPC and there is no server implementation.
- `web/src/components/MemoEditor/Editor/index.tsx:189-214`, `web/src/components/MemoEditor/Editor/useSuggestions.ts:28-158`, and `web/src/components/MemoEditor/Editor/TagSuggestions.tsx:10-49` provide a reusable textarea suggestion popup, but it is only instantiated for `#tag`.
- `web/src/components/MemoContent/index.tsx:53-136`, `web/src/utils/remark-plugins/remark-tag.ts:24-112`, and `web/src/components/MemoContent/Tag.tsx` parse and render `#tag` as a structured inline element; there is no `remarkMention` equivalent.
- `web/src/hooks/useUserQueries.ts:176-245` has `useListUsers()` for admin listing and `useUsersByNames()` for fetching known usernames one by one, but nothing that returns ranked candidates for an in-editor `@` query.
- `web/src/router/index.tsx:65-72` already routes public user profiles at `u/:username`, so inline mention rendering can target username-based profile URLs without inventing a new frontend route.
## Non-Goals
- Adding group mentions, team mentions, page mentions, or date mentions.
- Building a general “watch this memo/thread” subscription system beyond explicit mentions.
- Adding email, push, Slack, or webhook delivery for mentions in this issue.
- Redesigning memo visibility, access control, or per-user sharing semantics.
- Making old mentions follow username changes automatically.
- Redesigning the editor away from the current textarea-based implementation.
## Open Questions
- Which content surfaces are in scope for `@mention`? (default: top-level memos and memo comments, because both already share the same memo content pipeline)
- What mention token syntax should be recognized? (default: `@username` only, using canonical usernames rather than display names)
- Should edits trigger mention notifications after the initial create? (default: yes, but only for newly added mention targets compared with the memo’s previous mention set)
- What happens if someone types `@username` in content the target cannot access? (default: render the token as a mention in the author’s view, but do not send a notification unless the target can already access the memo/comment under existing visibility rules)
- Should mentioning yourself create an inbox item? (default: no, because self-mentions do not require attention routing)
- Should the mention candidate API be public like `GetUser`, or authenticated like the editor? (default: authenticated only, because ranked user search is a broader directory-enumeration surface than fetching a known public profile)
## Scope
**L** — The work crosses markdown parsing, memo payload extraction, memo create/update side effects, inbox and notification protos, user search APIs, three SQL drivers, React editor autocomplete, markdown rendering, and inbox UI. The repository already contains adjacent pieces for tags and comment notifications, but `@mention` requires stitching several existing subsystems together rather than extending a single isolated module.
## References
- [Comments, mentions & reactions - Notion Help Center](https://www.notion.com/help/comments-mentions-and-reminders)
- [Notification settings - Notion Help Center](https://www.notion.com/help/notification-settings)
- [Mention a person or team - Confluence Cloud](https://support.atlassian.com/confluence-cloud/docs/mention-a-person-or-team/)
- [Comment on Coda docs - Coda Help](https://help.coda.io/hc/en-us/articles/39555917053069-Comment-on-Coda-docs)
- [Customize notifications from comments - Coda Help](https://help.coda.io/hc/en-us/articles/39555901119117-Customize-notifications-from-comments)
## Industry Baseline
`Comments, mentions & reactions - Notion Help Center` shows the most common editor-side behavior: typing `@` triggers real-time search, mentions can live inline in page bodies and comments, clicking an inbox item takes the user back to the exact context, and no notification is sent when the target cannot access the page. `Notification settings - Notion Help Center` also separates in-product inbox behavior from secondary delivery like desktop or email.
`Mention a person or team - Confluence Cloud` adds two useful guardrails for a collaborative editor: autocomplete suggestions appear directly from `@`, and notifications are intentionally deduplicated so people are notified on the first mention rather than on every repeated mention in the same page.
`Comment on Coda docs` and `Customize notifications from comments` show a narrower scope for mentions inside comments, but reinforce two patterns that matter for Memos: explicit `@` mentions are a distinct notification trigger from generic participation, and products often keep mention notifications separate from broader thread-subscription or owner-subscription rules.
Across these products, the default implementation is not “parse arbitrary display text and hope it matches a user.” The stable interaction is: search among valid workspace members, insert a canonical mention token, render it differently from plain text, and only notify when access and deduplication rules say the event is meaningful.
## Research Summary
Memos already has the right extension points to adopt that baseline without a storage redesign. The backend has a custom inline markdown extension pipeline for `#tag`, memo create and update both rebuild `MemoPayload`, and the inbox model already represents user-facing attention items. The frontend editor already has a trigger-character suggestion popup, the markdown renderer already recognizes custom inline nodes, and public user profiles are already routed by username.
The biggest mismatch is user discovery. The current `ListUsers` path is admin-only and exact-match oriented, while mention autocomplete needs a normal authenticated user search API that can return ranked candidates by username and display name. The second mismatch is notification shape: the inbox and API layers only understand memo-comment notifications today, so a mention feature cannot be expressed as a first-class notification without extending the inbox proto and inbox UI.
Research also suggests that Memos should stay narrower than Notion or Confluence. There is no existing concept of teams, group mentions, page mentions, or per-page ACLs. The codebase already treats usernames as the public user token and memo visibility as a coarse `PUBLIC/PROTECTED/PRIVATE` rule. The best fit is therefore person mentions only, keyed by canonical username, with notification rules that are access-aware and deduplicated across repeated edits.
## Design Goals
- Typing `@` in the memo editor or comment editor shows ranked, authenticated user candidates and inserts a canonical `@username` token on selection.
- The backend extracts mention targets from memo/comment content during create, update, and payload rebuild, and produces the same mention set for equivalent content across all supported databases.
- Mention notifications are created only for newly added targets, at most once per target per memo revision, and never for self-mentions or inaccessible private content.
- Memo content renders resolved mentions as interactive inline entities and degrades unresolved tokens to plain text.
- The inbox API and inbox UI expose mention notifications as a first-class type distinct from comment notifications.
- The design does not require a relational schema migration; it only extends existing proto-backed JSON payloads and server/frontend code paths.
## Non-Goals
- Adding group mentions, team mentions, page mentions, or date mentions.
- Building a generic watch/subscription system for memo activity.
- Sending mention notifications through email, push, Slack, or webhooks.
- Making mention references survive username changes automatically.
- Replacing the textarea editor with a richer block editor.
- Redesigning memo visibility or introducing user-level memo sharing.
## Proposed Design
Support only canonical `@username` mentions in this issue. The parser should recognize the same username token vocabulary that the API already accepts for public user names, instead of trying to match display names or arbitrary free text. This keeps mention authoring aligned with existing user resource naming and avoids ambiguous matches when multiple users share similar display names. Mention suggestions may show both display name and username, but the inserted source text remains `@username`.
Add a backend markdown mention extension parallel to the existing tag extension. Introduce `internal/markdown/ast.MentionNode`, `internal/markdown/parser.NewMentionParser()`, and `internal/markdown/extensions.MentionExtension`, then wire it into `internal/markdown/markdown.go` next to `TagExtension`. The mention parser should require a word boundary before `@` so email addresses and URLs do not become mentions, and it should normalize the captured token to lowercase before lookup because usernames are canonicalized that way in the API layer.
Extend `storepb.MemoPayload` with a repeated mention metadata field, for example `repeated Mention mentions`, where each item stores at least `username` and resolved `user_id`. The raw markdown remains the source of truth for author-visible text, but the payload becomes the normalized server-side mention set for diffing and notification decisions. This reuses the existing memo payload rebuild path and avoids reparsing memo bodies in multiple side-effect handlers. No SQL migration is required because memo payloads are already stored as proto-backed JSON blobs in each database driver.
Teach `memopayload.RebuildMemoPayload` to resolve mention metadata while rebuilding tags and properties. The extraction step should walk the markdown AST once, collect raw `@username` tokens, resolve them to active users via the store, deduplicate by `user_id`, and populate `memo.Payload.Mentions`. Unresolved usernames should not fail memo creation; they should simply be omitted from normalized mention metadata so the feature remains tolerant of free-typed text. This mirrors how the frontend can degrade unresolved tokens back to plain text.
Add a dedicated mention side-effect helper around memo create and update flows. On create, after the memo is persisted and the final payload is available, compute the normalized mentioned user set from `memo.Payload.Mentions` and create inbox items for allowed targets. On update, diff the previous and new normalized mention sets and only notify targets that were newly added in the latest saved revision. This follows the Confluence-style deduplication pattern and prevents repeated notifications when a memo is edited without changing its mention set. If a mention is removed and later re-added, it counts as newly added again and may generate a fresh inbox item.
Apply access and duplication rules before writing inbox rows. Self-mentions are ignored. For top-level memos, notify only when the target can already read the memo under current visibility rules. For comments, notify the mentioned user when they can read the comment context and are not already covered by the existing memo-comment notification to the parent memo owner for that same event. This keeps mention notifications meaningful and avoids sending an owner both a comment notification and a mention notification for the same comment creation unless future product requirements explicitly want both. For `PRIVATE` memos and `PRIVATE` comments, mentions remain author-visible text but do not generate inbox notifications for other users.
Extend inbox storage and API notifications with a dedicated mention type instead of overloading the existing comment type. Add `MEMO_MENTION` to `proto/store/inbox.proto` with a payload that can represent both top-level memos and comments, such as `memo_id` plus optional `related_memo_id`. Mirror that in `proto/api/v1/user_service.proto` with `UserNotification_MEMO_MENTION` and `MemoMentionPayload`. Reuse the current notification conversion pattern in `server/router/api/v1/user_service.go`: resolve memo names from stored IDs, return a first-class mention payload, and let the inbox page render a separate mention card component. This keeps the notification center composable as new activity types appear.
Add an authenticated user-search endpoint specifically for mention autocomplete. The repository already has a stale public-method placeholder for `SearchUsers`, but no proto or handler. Define `SearchUsers` in `proto/api/v1/user_service.proto`, remove it from the public ACL list, and implement it in `server/router/api/v1/user_service.go` as an authenticated RPC that accepts a short query string plus page size. Extend `store.FindUser` with search-oriented fields and implement driver-specific case-insensitive matching in SQLite, MySQL, and PostgreSQL over `username` and `nickname`, ordered by exact username match, username prefix, nickname prefix, then a stable fallback. This produces a usable editor candidate list without reusing the admin-only `ListUsers` contract.
Implement frontend mention suggestions by reusing the existing generic textarea suggestion system. Add a `MentionSuggestions` component beside `TagSuggestions`, hook it into `web/src/components/MemoEditor/Editor/index.tsx`, and back it with a debounced `useSearchUsers(query)` hook. The popup should render avatar, display name, and `@username`, while selection inserts `@username ` exactly. Because `useSuggestions` currently operates on local item arrays, it can stay generic if the mention hook owns the remote query and passes the current ranked results down as `items`.
Implement frontend mention rendering with a dedicated markdown plugin and component instead of trying to infer mentions from links or plain spans. Add `remarkMention` beside `remarkTag`, a `Mention` inline component beside `Tag`, and a mention type guard in `web/src/types/markdown.ts`. The renderer should link resolved mentions to `/u/:username`, show display name or username with avatar-based affordance when lookup data is available, and render unresolved mention text non-interactively. To avoid N-per-mention network fetches, `MemoContent` should collect mentioned usernames from content and hydrate them through the existing `useUsersByNames()` hook once per memo render tree.
Render mention notifications as their own inbox card. Reuse the existing `MemoCommentMessage` pattern, but resolve the source memo/comment and optional related memo from the `MemoMentionPayload`. The card should show who mentioned the user, in what memo or comment, a short snippet, and navigate to the relevant memo detail on click. `web/src/pages/Inboxes.tsx` should switch on both `MEMO_COMMENT` and `MEMO_MENTION` so the inbox can grow by type without silently discarding new notifications.
Do not solve username drift in this issue. If a user later changes username, existing raw markdown still contains the old `@username` text, and rebuilt payload metadata will stop resolving unless the old token still matches a live username. This is acceptable for the current scope because username-history and alias resolution are already out of scope elsewhere in the codebase. The alternative of storing opaque mention IDs in source markdown or adding a username-alias subsystem was rejected because it turns a contained collaboration feature into a broader identity migration project.
## Execution Log
### T1: Add backend mention parsing and payload extraction
**Status**: Completed
**Files Changed**: `internal/markdown/ast/mention.go`, `internal/markdown/parser/mention.go`, `internal/markdown/extensions/mention.go`, `internal/markdown/markdown.go`, `internal/markdown/renderer/markdown_renderer.go`, `server/runner/memopayload/runner.go`, `server/router/api/v1/memo_service.go`, `server/router/api/v1/v1.go`, `server/router/api/v1/test/test_helper.go`, `internal/markdown/markdown_test.go`
**Validation**: `go test ./internal/markdown` — PASS
**Path Corrections**: `RebuildMemoPayload` needed `context + store` so mention resolution could happen during payload rebuild.
**Deviations**: None
### T2: Add mention notifications and user search APIs
**Status**: Completed
**Files Changed**: `proto/store/memo.proto`, `proto/store/inbox.proto`, `proto/api/v1/user_service.proto`, `server/router/api/v1/user_service.go`, `server/router/api/v1/connect_services.go`, `server/router/api/v1/acl_config.go`, `server/router/api/v1/acl_config_test.go`, `server/router/api/v1/memo_mention_helpers.go`, `store/user.go`, `store/db/sqlite/user.go`, `store/db/postgres/user.go`, `store/db/mysql/user.go`, `server/router/api/v1/test/user_notification_test.go`, `server/router/api/v1/test/user_search_test.go`
**Validation**: `go test ./server/router/api/v1/...` — PASS
**Path Corrections**: Unknown legacy inbox message types are filtered server-side to keep unread counts aligned with rendered cards.
**Deviations**: None
### T3: Add frontend mention autocomplete, rendering, and inbox UI
**Status**: Completed
**Files Changed**: `web/src/components/MemoEditor/Editor/MentionSuggestions.tsx`, `web/src/components/MemoEditor/Editor/index.tsx`, `web/src/components/MemoEditor/Editor/useSuggestions.ts`, `web/src/hooks/useUserQueries.ts`, `web/src/utils/remark-plugins/remark-mention.ts`, `web/src/components/MemoContent/MentionContext.tsx`, `web/src/components/MemoContent/Mention.tsx`, `web/src/components/MemoContent/index.tsx`, `web/src/components/MemoContent/ConditionalComponent.tsx`, `web/src/types/markdown.ts`, `web/src/components/Inbox/MemoMentionMessage.tsx`, `web/src/pages/Inboxes.tsx`
**Validation**: `pnpm lint && pnpm build` — PASS
**Path Corrections**: Editor autocomplete reused the existing generic suggestion hook by exposing the live query rather than duplicating keyboard navigation logic.
**Deviations**: None
### T4: Regenerate code and validate the feature
**Status**: Completed
**Files Changed**: `proto/gen/**`, `web/src/types/proto/**`
**Validation**: `buf generate` — PASS; `go test ./internal/markdown ./server/router/api/v1/...` — PASS; `pnpm lint` — PASS; `pnpm build` — PASS
**Path Corrections**: None
**Deviations**: None
## Completion Declaration
All tasks completed successfully
## Task List
### Task Index
T1: Add backend mention parsing and payload extraction [M] — T2: Add mention notifications and user search APIs [L] — T3: Add frontend mention autocomplete, rendering, and inbox UI [L] — T4: Regenerate code and validate the feature [M]
### T1: Add backend mention parsing and payload extraction [M]
**Objective**: Parse `@username` tokens into structured mention metadata during memo payload rebuilds.
**Size**: M
**Files**:
- Create: `internal/markdown/ast/mention.go`
- Create: `internal/markdown/parser/mention.go`
- Create: `internal/markdown/extensions/mention.go`
- Modify: `internal/markdown/markdown.go`
- Modify: `internal/markdown/renderer/markdown_renderer.go`
- Modify: `server/runner/memopayload/runner.go`
- Modify: `server/router/api/v1/memo_service.go`
- Test: `internal/markdown/markdown_test.go`
**Implementation**:
1. Add mention AST/parser/extension parallel to the existing tag implementation.
2. Extend extracted markdown data and `MemoPayload` rebuild to collect normalized mentions and resolve them to users.
3. Update memo create/update and background payload rebuild paths to use the new mention-aware payload builder.
**Boundaries**: Do not add a relational schema migration.
**Dependencies**: None
**Expected Outcome**: Memo payloads carry normalized mention metadata rebuilt from markdown content.
**Validation**: `go test ./internal/markdown` — expected `ok`
### T2: Add mention notifications and user search APIs [L]
**Objective**: Expose mention-aware APIs and create inbox items for newly added mentions.
**Size**: L
**Files**:
- Modify: `proto/store/memo.proto`
- Modify: `proto/store/inbox.proto`
- Modify: `proto/api/v1/user_service.proto`
- Modify: `server/router/api/v1/user_service.go`
- Modify: `server/router/api/v1/connect_services.go`
- Modify: `server/router/api/v1/acl_config.go`
- Modify: `server/router/api/v1/acl_config_test.go`
- Create: `server/router/api/v1/memo_mention_helpers.go`
- Modify: `store/user.go`
- Modify: `store/db/sqlite/user.go`
- Modify: `store/db/postgres/user.go`
- Modify: `store/db/mysql/user.go`
- Test: `server/router/api/v1/test/user_notification_test.go`
- Test: `server/router/api/v1/test/user_search_test.go`
**Implementation**:
1. Extend proto contracts with `MemoPayload.mentions`, `InboxMessage.MEMO_MENTION`, `UserNotification.MEMO_MENTION`, and `SearchUsers`.
2. Implement authenticated user search over username and nickname.
3. Add mention notification side effects for memo create/update/comment flows with diffing and duplicate suppression.
4. Convert inbox rows into either comment or mention notifications and filter unknown legacy types.
**Boundaries**: Do not add email/push/webhook mention delivery.
**Dependencies**: T1
**Expected Outcome**: Mentioned users receive inbox notifications and the editor has an API to fetch mention candidates.
**Validation**: `go test ./server/router/api/v1/...` — expected `ok`
### T3: Add frontend mention autocomplete, rendering, and inbox UI [L]
**Objective**: Let users insert mentions from the editor and render/read them in the UI.
**Size**: L
**Files**:
- Create: `web/src/components/MemoEditor/Editor/MentionSuggestions.tsx`
- Modify: `web/src/components/MemoEditor/Editor/index.tsx`
- Modify: `web/src/components/MemoEditor/Editor/useSuggestions.ts`
- Modify: `web/src/hooks/useUserQueries.ts`
- Create: `web/src/utils/remark-plugins/remark-mention.ts`
- Create: `web/src/components/MemoContent/MentionContext.tsx`
- Create: `web/src/components/MemoContent/Mention.tsx`
- Modify: `web/src/components/MemoContent/index.tsx`
- Modify: `web/src/components/MemoContent/ConditionalComponent.tsx`
- Modify: `web/src/types/markdown.ts`
- Create: `web/src/components/Inbox/MemoMentionMessage.tsx`
- Modify: `web/src/pages/Inboxes.tsx`
**Implementation**:
1. Add `@` autocomplete backed by `SearchUsers`.
2. Add markdown mention parsing/rendering and hydrate mentioned users once per memo render.
3. Add a dedicated inbox card for memo mention notifications.
**Boundaries**: Do not redesign the textarea editor.
**Dependencies**: T2
**Expected Outcome**: Users can insert, see, and open mentions from memo content and inbox notifications.
**Validation**: `pnpm lint && pnpm build` — expected success
### T4: Regenerate code and validate the feature [M]
**Objective**: Regenerate generated code and verify backend/frontend behavior.
**Size**: M
**Files**:
- Modify: `proto/gen/**`
- Modify: `web/src/types/proto/**`
**Implementation**:
1. Run `buf generate` after proto changes.
2. Re-run focused Go tests and frontend lint/build.
**Boundaries**: Do not broaden into unrelated CI cleanup.
**Dependencies**: T1, T2, T3
**Expected Outcome**: Generated code matches the new APIs and validations pass.
**Validation**: `buf generate`, `go test ./internal/markdown ./server/router/api/v1/...`, `pnpm lint`, `pnpm build`
## Out-of-Scope Tasks
- Group/team mentions
- Username alias migration
- Email or push delivery for mentions
- Watch/subscription semantics beyond explicit mentions
package ast
import (
gast "github.com/yuin/goldmark/ast"
)
// MentionNode represents an @mention in the markdown AST.
type MentionNode struct {
gast.BaseInline
// Username without the @ prefix.
Username []byte
}
// KindMention is the NodeKind for MentionNode.
var KindMention = gast.NewNodeKind("Mention")
// Kind returns KindMention.
func (*MentionNode) Kind() gast.NodeKind {
return KindMention
}
// Dump implements Node.Dump for debugging.
func (n *MentionNode) Dump(source []byte, level int) {
gast.DumpHelper(n, source, level, map[string]string{
"Username": string(n.Username),
}, nil)
}
package extensions
import (
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/util"
mparser "github.com/usememos/memos/internal/markdown/parser"
)
type mentionExtension struct{}
// MentionExtension is a goldmark extension for @mention syntax.
var MentionExtension = &mentionExtension{}
// Extend extends the goldmark parser with mention support.
func (*mentionExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(
parser.WithInlineParsers(
// Priority 200 - run before standard link parser (500).
util.Prioritized(mparser.NewMentionParser(), 200),
),
)
}
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
// ExtractedData contains all metadata extracted from markdown in a single pass. // ExtractedData contains all metadata extracted from markdown in a single pass.
type ExtractedData struct { type ExtractedData struct {
Tags []string Tags []string
Mentions []string
Property *storepb.MemoPayload_Property Property *storepb.MemoPayload_Property
} }
...@@ -63,6 +64,7 @@ type Option func(*config) ...@@ -63,6 +64,7 @@ type Option func(*config)
type config struct { type config struct {
enableTags bool enableTags bool
enableMentions bool
} }
// WithTagExtension enables #tag parsing. // WithTagExtension enables #tag parsing.
...@@ -72,6 +74,13 @@ func WithTagExtension() Option { ...@@ -72,6 +74,13 @@ func WithTagExtension() Option {
} }
} }
// WithMentionExtension enables @mention parsing.
func WithMentionExtension() Option {
return func(c *config) {
c.enableMentions = true
}
}
// NewService creates a new markdown service with the given options. // NewService creates a new markdown service with the given options.
func NewService(opts ...Option) Service { func NewService(opts ...Option) Service {
cfg := &config{} cfg := &config{}
...@@ -87,6 +96,9 @@ func NewService(opts ...Option) Service { ...@@ -87,6 +96,9 @@ func NewService(opts ...Option) Service {
if cfg.enableTags { if cfg.enableTags {
exts = append(exts, extensions.TagExtension) exts = append(exts, extensions.TagExtension)
} }
if cfg.enableMentions {
exts = append(exts, extensions.MentionExtension)
}
md := goldmark.New( md := goldmark.New(
goldmark.WithExtensions(exts...), goldmark.WithExtensions(exts...),
...@@ -330,6 +342,7 @@ func (s *service) ExtractAll(content []byte) (*ExtractedData, error) { ...@@ -330,6 +342,7 @@ func (s *service) ExtractAll(content []byte) (*ExtractedData, error) {
data := &ExtractedData{ data := &ExtractedData{
Tags: []string{}, Tags: []string{},
Mentions: []string{},
Property: &storepb.MemoPayload_Property{}, Property: &storepb.MemoPayload_Property{},
} }
...@@ -345,6 +358,9 @@ func (s *service) ExtractAll(content []byte) (*ExtractedData, error) { ...@@ -345,6 +358,9 @@ func (s *service) ExtractAll(content []byte) (*ExtractedData, error) {
if tagNode, ok := n.(*mast.TagNode); ok { if tagNode, ok := n.(*mast.TagNode); ok {
data.Tags = append(data.Tags, string(tagNode.Tag)) data.Tags = append(data.Tags, string(tagNode.Tag))
} }
if mentionNode, ok := n.(*mast.MentionNode); ok {
data.Mentions = append(data.Mentions, strings.ToLower(string(mentionNode.Username)))
}
// Check if the first block-level child of the document is an H1 heading. // Check if the first block-level child of the document is an H1 heading.
if !firstBlockChecked && n.Parent() != nil && n.Parent().Kind() == gast.KindDocument { if !firstBlockChecked && n.Parent() != nil && n.Parent().Kind() == gast.KindDocument {
...@@ -382,6 +398,7 @@ func (s *service) ExtractAll(content []byte) (*ExtractedData, error) { ...@@ -382,6 +398,7 @@ func (s *service) ExtractAll(content []byte) (*ExtractedData, error) {
// Deduplicate tags while preserving original case // Deduplicate tags while preserving original case
data.Tags = uniquePreserveCase(data.Tags) data.Tags = uniquePreserveCase(data.Tags)
data.Mentions = uniquePreserveCase(data.Mentions)
return data, nil return data, nil
} }
......
...@@ -340,6 +340,15 @@ func TestExtractAllTitle(t *testing.T) { ...@@ -340,6 +340,15 @@ func TestExtractAllTitle(t *testing.T) {
} }
} }
func TestExtractAllMentions(t *testing.T) {
svc := NewService(WithTagExtension(), WithMentionExtension())
data, err := svc.ExtractAll([]byte("Hi @Alice and @bob. Email support@example.com should stay plain. #tag"))
require.NoError(t, err)
assert.ElementsMatch(t, []string{"alice", "bob"}, data.Mentions)
assert.ElementsMatch(t, []string{"tag"}, data.Tags)
}
func TestExtractTags(t *testing.T) { func TestExtractTags(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
......
package parser
import (
"unicode"
"unicode/utf8"
gast "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
mast "github.com/usememos/memos/internal/markdown/ast"
)
const (
// MaxMentionLength matches the username token length accepted by the API.
MaxMentionLength = 32
)
type mentionParser struct{}
// NewMentionParser creates a new inline parser for @mention syntax.
func NewMentionParser() parser.InlineParser {
return &mentionParser{}
}
// Trigger returns the characters that trigger this parser.
func (*mentionParser) Trigger() []byte {
return []byte{'@'}
}
func isValidMentionRune(r rune) bool {
return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '-'
}
func isMentionBoundary(r rune) bool {
return unicode.IsSpace(r) || unicode.IsPunct(r) || unicode.IsSymbol(r)
}
// Parse parses @mention syntax while avoiding email-address matches.
func (*mentionParser) Parse(_ gast.Node, block text.Reader, _ parser.Context) gast.Node {
line, _ := block.PeekLine()
if len(line) == 0 || line[0] != '@' {
return nil
}
prev := block.PrecendingCharacter()
if prev != '\n' && !isMentionBoundary(prev) {
return nil
}
start := 1
pos := start
runeCount := 0
hasLetterOrNumber := false
for pos < len(line) {
r, size := utf8.DecodeRune(line[pos:])
if r == utf8.RuneError && size == 1 {
break
}
if !isValidMentionRune(r) {
break
}
if unicode.IsLetter(r) || unicode.IsNumber(r) {
hasLetterOrNumber = true
}
runeCount++
if runeCount > MaxMentionLength {
break
}
pos += size
}
if pos <= start || !hasLetterOrNumber {
return nil
}
username := line[start:pos]
usernameCopy := make([]byte, len(username))
copy(usernameCopy, username)
block.Advance(pos)
return &mast.MentionNode{
Username: usernameCopy,
}
}
...@@ -156,6 +156,10 @@ func (r *MarkdownRenderer) renderNode(node gast.Node, source []byte, depth int) ...@@ -156,6 +156,10 @@ func (r *MarkdownRenderer) renderNode(node gast.Node, source []byte, depth int)
r.buf.WriteByte('#') r.buf.WriteByte('#')
r.buf.Write(n.Tag) r.buf.Write(n.Tag)
case *mast.MentionNode:
r.buf.WriteByte('@')
r.buf.Write(n.Username)
default: default:
// For unknown nodes, try to render children // For unknown nodes, try to render children
r.renderChildren(n, source, depth) r.renderChildren(n, source, depth)
......
...@@ -19,6 +19,14 @@ service UserService { ...@@ -19,6 +19,14 @@ service UserService {
option (google.api.http) = {get: "/api/v1/users"}; option (google.api.http) = {get: "/api/v1/users"};
} }
// BatchGetUsers returns active users by usernames.
rpc BatchGetUsers(BatchGetUsersRequest) returns (BatchGetUsersResponse) {
option (google.api.http) = {
post: "/api/v1/users:batchGet"
body: "*"
};
}
// GetUser gets a user by username. // GetUser gets a user by username.
// Format: users/{username} (e.g., users/steven) // Format: users/{username} (e.g., users/steven)
rpc GetUser(GetUserRequest) returns (User) { rpc GetUser(GetUserRequest) returns (User) {
...@@ -242,6 +250,14 @@ message ListUsersResponse { ...@@ -242,6 +250,14 @@ message ListUsersResponse {
int32 total_size = 3; int32 total_size = 3;
} }
message BatchGetUsersRequest {
repeated string usernames = 1;
}
message BatchGetUsersResponse {
repeated User users = 1;
}
message GetUserRequest { message GetUserRequest {
// Required. The resource name of the user. // Required. The resource name of the user.
// Format: users/{username} // Format: users/{username}
...@@ -612,6 +628,9 @@ message UserNotification { ...@@ -612,6 +628,9 @@ message UserNotification {
(google.api.resource_reference) = {type: "memos.api.v1/User"} (google.api.resource_reference) = {type: "memos.api.v1/User"}
]; ];
// The sender user details.
User sender_user = 8 [(google.api.field_behavior) = OUTPUT_ONLY];
// The status of the notification. // The status of the notification.
Status status = 3 [(google.api.field_behavior) = OPTIONAL]; Status status = 3 [(google.api.field_behavior) = OPTIONAL];
...@@ -623,6 +642,7 @@ message UserNotification { ...@@ -623,6 +642,7 @@ message UserNotification {
oneof payload { oneof payload {
MemoCommentPayload memo_comment = 6 [(google.api.field_behavior) = OUTPUT_ONLY]; MemoCommentPayload memo_comment = 6 [(google.api.field_behavior) = OUTPUT_ONLY];
MemoMentionPayload memo_mention = 7 [(google.api.field_behavior) = OUTPUT_ONLY];
} }
message MemoCommentPayload { message MemoCommentPayload {
...@@ -633,6 +653,28 @@ message UserNotification { ...@@ -633,6 +653,28 @@ message UserNotification {
// The name of related memo. // The name of related memo.
// Format: memos/{memo} // Format: memos/{memo}
string related_memo = 2; string related_memo = 2;
// Preview text of the comment memo.
string memo_snippet = 3;
// Preview text of the related memo.
string related_memo_snippet = 4;
}
message MemoMentionPayload {
// The memo that contains the mention.
// Format: memos/{memo}
string memo = 1;
// The related parent memo when the mention was created in a comment.
// Format: memos/{memo}
string related_memo = 2;
// Preview text of the memo that contains the mention.
string memo_snippet = 3;
// Preview text of the related parent memo.
string related_memo_snippet = 4;
} }
enum Status { enum Status {
...@@ -644,6 +686,7 @@ message UserNotification { ...@@ -644,6 +686,7 @@ message UserNotification {
enum Type { enum Type {
TYPE_UNSPECIFIED = 0; TYPE_UNSPECIFIED = 0;
MEMO_COMMENT = 1; MEMO_COMMENT = 1;
MEMO_MENTION = 2;
} }
} }
......
...@@ -36,6 +36,9 @@ const ( ...@@ -36,6 +36,9 @@ const (
const ( const (
// UserServiceListUsersProcedure is the fully-qualified name of the UserService's ListUsers RPC. // UserServiceListUsersProcedure is the fully-qualified name of the UserService's ListUsers RPC.
UserServiceListUsersProcedure = "/memos.api.v1.UserService/ListUsers" UserServiceListUsersProcedure = "/memos.api.v1.UserService/ListUsers"
// UserServiceBatchGetUsersProcedure is the fully-qualified name of the UserService's BatchGetUsers
// RPC.
UserServiceBatchGetUsersProcedure = "/memos.api.v1.UserService/BatchGetUsers"
// UserServiceGetUserProcedure is the fully-qualified name of the UserService's GetUser RPC. // UserServiceGetUserProcedure is the fully-qualified name of the UserService's GetUser RPC.
UserServiceGetUserProcedure = "/memos.api.v1.UserService/GetUser" UserServiceGetUserProcedure = "/memos.api.v1.UserService/GetUser"
// UserServiceCreateUserProcedure is the fully-qualified name of the UserService's CreateUser RPC. // UserServiceCreateUserProcedure is the fully-qualified name of the UserService's CreateUser RPC.
...@@ -95,6 +98,8 @@ const ( ...@@ -95,6 +98,8 @@ const (
type UserServiceClient interface { type UserServiceClient interface {
// ListUsers returns a list of users. // ListUsers returns a list of users.
ListUsers(context.Context, *connect.Request[v1.ListUsersRequest]) (*connect.Response[v1.ListUsersResponse], error) ListUsers(context.Context, *connect.Request[v1.ListUsersRequest]) (*connect.Response[v1.ListUsersResponse], error)
// BatchGetUsers returns active users by usernames.
BatchGetUsers(context.Context, *connect.Request[v1.BatchGetUsersRequest]) (*connect.Response[v1.BatchGetUsersResponse], error)
// GetUser gets a user by username. // GetUser gets a user by username.
// Format: users/{username} (e.g., users/steven) // Format: users/{username} (e.g., users/steven)
GetUser(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error) GetUser(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error)
...@@ -155,6 +160,12 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. ...@@ -155,6 +160,12 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts ..
connect.WithSchema(userServiceMethods.ByName("ListUsers")), connect.WithSchema(userServiceMethods.ByName("ListUsers")),
connect.WithClientOptions(opts...), connect.WithClientOptions(opts...),
), ),
batchGetUsers: connect.NewClient[v1.BatchGetUsersRequest, v1.BatchGetUsersResponse](
httpClient,
baseURL+UserServiceBatchGetUsersProcedure,
connect.WithSchema(userServiceMethods.ByName("BatchGetUsers")),
connect.WithClientOptions(opts...),
),
getUser: connect.NewClient[v1.GetUserRequest, v1.User]( getUser: connect.NewClient[v1.GetUserRequest, v1.User](
httpClient, httpClient,
baseURL+UserServiceGetUserProcedure, baseURL+UserServiceGetUserProcedure,
...@@ -275,6 +286,7 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. ...@@ -275,6 +286,7 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts ..
// userServiceClient implements UserServiceClient. // userServiceClient implements UserServiceClient.
type userServiceClient struct { type userServiceClient struct {
listUsers *connect.Client[v1.ListUsersRequest, v1.ListUsersResponse] listUsers *connect.Client[v1.ListUsersRequest, v1.ListUsersResponse]
batchGetUsers *connect.Client[v1.BatchGetUsersRequest, v1.BatchGetUsersResponse]
getUser *connect.Client[v1.GetUserRequest, v1.User] getUser *connect.Client[v1.GetUserRequest, v1.User]
createUser *connect.Client[v1.CreateUserRequest, v1.User] createUser *connect.Client[v1.CreateUserRequest, v1.User]
updateUser *connect.Client[v1.UpdateUserRequest, v1.User] updateUser *connect.Client[v1.UpdateUserRequest, v1.User]
...@@ -301,6 +313,11 @@ func (c *userServiceClient) ListUsers(ctx context.Context, req *connect.Request[ ...@@ -301,6 +313,11 @@ func (c *userServiceClient) ListUsers(ctx context.Context, req *connect.Request[
return c.listUsers.CallUnary(ctx, req) return c.listUsers.CallUnary(ctx, req)
} }
// BatchGetUsers calls memos.api.v1.UserService.BatchGetUsers.
func (c *userServiceClient) BatchGetUsers(ctx context.Context, req *connect.Request[v1.BatchGetUsersRequest]) (*connect.Response[v1.BatchGetUsersResponse], error) {
return c.batchGetUsers.CallUnary(ctx, req)
}
// GetUser calls memos.api.v1.UserService.GetUser. // GetUser calls memos.api.v1.UserService.GetUser.
func (c *userServiceClient) GetUser(ctx context.Context, req *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error) { func (c *userServiceClient) GetUser(ctx context.Context, req *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error) {
return c.getUser.CallUnary(ctx, req) return c.getUser.CallUnary(ctx, req)
...@@ -400,6 +417,8 @@ func (c *userServiceClient) DeleteUserNotification(ctx context.Context, req *con ...@@ -400,6 +417,8 @@ func (c *userServiceClient) DeleteUserNotification(ctx context.Context, req *con
type UserServiceHandler interface { type UserServiceHandler interface {
// ListUsers returns a list of users. // ListUsers returns a list of users.
ListUsers(context.Context, *connect.Request[v1.ListUsersRequest]) (*connect.Response[v1.ListUsersResponse], error) ListUsers(context.Context, *connect.Request[v1.ListUsersRequest]) (*connect.Response[v1.ListUsersResponse], error)
// BatchGetUsers returns active users by usernames.
BatchGetUsers(context.Context, *connect.Request[v1.BatchGetUsersRequest]) (*connect.Response[v1.BatchGetUsersResponse], error)
// GetUser gets a user by username. // GetUser gets a user by username.
// Format: users/{username} (e.g., users/steven) // Format: users/{username} (e.g., users/steven)
GetUser(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error) GetUser(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error)
...@@ -456,6 +475,12 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption ...@@ -456,6 +475,12 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption
connect.WithSchema(userServiceMethods.ByName("ListUsers")), connect.WithSchema(userServiceMethods.ByName("ListUsers")),
connect.WithHandlerOptions(opts...), connect.WithHandlerOptions(opts...),
) )
userServiceBatchGetUsersHandler := connect.NewUnaryHandler(
UserServiceBatchGetUsersProcedure,
svc.BatchGetUsers,
connect.WithSchema(userServiceMethods.ByName("BatchGetUsers")),
connect.WithHandlerOptions(opts...),
)
userServiceGetUserHandler := connect.NewUnaryHandler( userServiceGetUserHandler := connect.NewUnaryHandler(
UserServiceGetUserProcedure, UserServiceGetUserProcedure,
svc.GetUser, svc.GetUser,
...@@ -574,6 +599,8 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption ...@@ -574,6 +599,8 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption
switch r.URL.Path { switch r.URL.Path {
case UserServiceListUsersProcedure: case UserServiceListUsersProcedure:
userServiceListUsersHandler.ServeHTTP(w, r) userServiceListUsersHandler.ServeHTTP(w, r)
case UserServiceBatchGetUsersProcedure:
userServiceBatchGetUsersHandler.ServeHTTP(w, r)
case UserServiceGetUserProcedure: case UserServiceGetUserProcedure:
userServiceGetUserHandler.ServeHTTP(w, r) userServiceGetUserHandler.ServeHTTP(w, r)
case UserServiceCreateUserProcedure: case UserServiceCreateUserProcedure:
...@@ -625,6 +652,10 @@ func (UnimplementedUserServiceHandler) ListUsers(context.Context, *connect.Reque ...@@ -625,6 +652,10 @@ func (UnimplementedUserServiceHandler) ListUsers(context.Context, *connect.Reque
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.ListUsers is not implemented")) return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.ListUsers is not implemented"))
} }
func (UnimplementedUserServiceHandler) BatchGetUsers(context.Context, *connect.Request[v1.BatchGetUsersRequest]) (*connect.Response[v1.BatchGetUsersResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.BatchGetUsers is not implemented"))
}
func (UnimplementedUserServiceHandler) GetUser(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error) { func (UnimplementedUserServiceHandler) GetUser(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.User], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.GetUser is not implemented")) return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.UserService.GetUser is not implemented"))
} }
......
...@@ -126,7 +126,7 @@ func (x UserSetting_Key) Number() protoreflect.EnumNumber { ...@@ -126,7 +126,7 @@ func (x UserSetting_Key) Number() protoreflect.EnumNumber {
// Deprecated: Use UserSetting_Key.Descriptor instead. // Deprecated: Use UserSetting_Key.Descriptor instead.
func (UserSetting_Key) EnumDescriptor() ([]byte, []int) { func (UserSetting_Key) EnumDescriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{11, 0} return file_api_v1_user_service_proto_rawDescGZIP(), []int{13, 0}
} }
type UserNotification_Status int32 type UserNotification_Status int32
...@@ -175,7 +175,7 @@ func (x UserNotification_Status) Number() protoreflect.EnumNumber { ...@@ -175,7 +175,7 @@ func (x UserNotification_Status) Number() protoreflect.EnumNumber {
// Deprecated: Use UserNotification_Status.Descriptor instead. // Deprecated: Use UserNotification_Status.Descriptor instead.
func (UserNotification_Status) EnumDescriptor() ([]byte, []int) { func (UserNotification_Status) EnumDescriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{28, 0} return file_api_v1_user_service_proto_rawDescGZIP(), []int{30, 0}
} }
type UserNotification_Type int32 type UserNotification_Type int32
...@@ -183,6 +183,7 @@ type UserNotification_Type int32 ...@@ -183,6 +183,7 @@ type UserNotification_Type int32
const ( const (
UserNotification_TYPE_UNSPECIFIED UserNotification_Type = 0 UserNotification_TYPE_UNSPECIFIED UserNotification_Type = 0
UserNotification_MEMO_COMMENT UserNotification_Type = 1 UserNotification_MEMO_COMMENT UserNotification_Type = 1
UserNotification_MEMO_MENTION UserNotification_Type = 2
) )
// Enum value maps for UserNotification_Type. // Enum value maps for UserNotification_Type.
...@@ -190,10 +191,12 @@ var ( ...@@ -190,10 +191,12 @@ var (
UserNotification_Type_name = map[int32]string{ UserNotification_Type_name = map[int32]string{
0: "TYPE_UNSPECIFIED", 0: "TYPE_UNSPECIFIED",
1: "MEMO_COMMENT", 1: "MEMO_COMMENT",
2: "MEMO_MENTION",
} }
UserNotification_Type_value = map[string]int32{ UserNotification_Type_value = map[string]int32{
"TYPE_UNSPECIFIED": 0, "TYPE_UNSPECIFIED": 0,
"MEMO_COMMENT": 1, "MEMO_COMMENT": 1,
"MEMO_MENTION": 2,
} }
) )
...@@ -221,7 +224,7 @@ func (x UserNotification_Type) Number() protoreflect.EnumNumber { ...@@ -221,7 +224,7 @@ func (x UserNotification_Type) Number() protoreflect.EnumNumber {
// Deprecated: Use UserNotification_Type.Descriptor instead. // Deprecated: Use UserNotification_Type.Descriptor instead.
func (UserNotification_Type) EnumDescriptor() ([]byte, []int) { func (UserNotification_Type) EnumDescriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{28, 1} return file_api_v1_user_service_proto_rawDescGZIP(), []int{30, 1}
} }
type User struct { type User struct {
...@@ -503,6 +506,94 @@ func (x *ListUsersResponse) GetTotalSize() int32 { ...@@ -503,6 +506,94 @@ func (x *ListUsersResponse) GetTotalSize() int32 {
return 0 return 0
} }
type BatchGetUsersRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Usernames []string `protobuf:"bytes,1,rep,name=usernames,proto3" json:"usernames,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BatchGetUsersRequest) Reset() {
*x = BatchGetUsersRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *BatchGetUsersRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BatchGetUsersRequest) ProtoMessage() {}
func (x *BatchGetUsersRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BatchGetUsersRequest.ProtoReflect.Descriptor instead.
func (*BatchGetUsersRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{3}
}
func (x *BatchGetUsersRequest) GetUsernames() []string {
if x != nil {
return x.Usernames
}
return nil
}
type BatchGetUsersResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Users []*User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BatchGetUsersResponse) Reset() {
*x = BatchGetUsersResponse{}
mi := &file_api_v1_user_service_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *BatchGetUsersResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BatchGetUsersResponse) ProtoMessage() {}
func (x *BatchGetUsersResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BatchGetUsersResponse.ProtoReflect.Descriptor instead.
func (*BatchGetUsersResponse) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{4}
}
func (x *BatchGetUsersResponse) GetUsers() []*User {
if x != nil {
return x.Users
}
return nil
}
type GetUserRequest struct { type GetUserRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// Required. The resource name of the user. // Required. The resource name of the user.
...@@ -517,7 +608,7 @@ type GetUserRequest struct { ...@@ -517,7 +608,7 @@ type GetUserRequest struct {
func (x *GetUserRequest) Reset() { func (x *GetUserRequest) Reset() {
*x = GetUserRequest{} *x = GetUserRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[3] mi := &file_api_v1_user_service_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -529,7 +620,7 @@ func (x *GetUserRequest) String() string { ...@@ -529,7 +620,7 @@ func (x *GetUserRequest) String() string {
func (*GetUserRequest) ProtoMessage() {} func (*GetUserRequest) ProtoMessage() {}
func (x *GetUserRequest) ProtoReflect() protoreflect.Message { func (x *GetUserRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[3] mi := &file_api_v1_user_service_proto_msgTypes[5]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -542,7 +633,7 @@ func (x *GetUserRequest) ProtoReflect() protoreflect.Message { ...@@ -542,7 +633,7 @@ func (x *GetUserRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetUserRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetUserRequest.ProtoReflect.Descriptor instead.
func (*GetUserRequest) Descriptor() ([]byte, []int) { func (*GetUserRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{3} return file_api_v1_user_service_proto_rawDescGZIP(), []int{5}
} }
func (x *GetUserRequest) GetName() string { func (x *GetUserRequest) GetName() string {
...@@ -578,7 +669,7 @@ type CreateUserRequest struct { ...@@ -578,7 +669,7 @@ type CreateUserRequest struct {
func (x *CreateUserRequest) Reset() { func (x *CreateUserRequest) Reset() {
*x = CreateUserRequest{} *x = CreateUserRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[4] mi := &file_api_v1_user_service_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -590,7 +681,7 @@ func (x *CreateUserRequest) String() string { ...@@ -590,7 +681,7 @@ func (x *CreateUserRequest) String() string {
func (*CreateUserRequest) ProtoMessage() {} func (*CreateUserRequest) ProtoMessage() {}
func (x *CreateUserRequest) ProtoReflect() protoreflect.Message { func (x *CreateUserRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[4] mi := &file_api_v1_user_service_proto_msgTypes[6]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -603,7 +694,7 @@ func (x *CreateUserRequest) ProtoReflect() protoreflect.Message { ...@@ -603,7 +694,7 @@ func (x *CreateUserRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateUserRequest.ProtoReflect.Descriptor instead. // Deprecated: Use CreateUserRequest.ProtoReflect.Descriptor instead.
func (*CreateUserRequest) Descriptor() ([]byte, []int) { func (*CreateUserRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{4} return file_api_v1_user_service_proto_rawDescGZIP(), []int{6}
} }
func (x *CreateUserRequest) GetUser() *User { func (x *CreateUserRequest) GetUser() *User {
...@@ -648,7 +739,7 @@ type UpdateUserRequest struct { ...@@ -648,7 +739,7 @@ type UpdateUserRequest struct {
func (x *UpdateUserRequest) Reset() { func (x *UpdateUserRequest) Reset() {
*x = UpdateUserRequest{} *x = UpdateUserRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[5] mi := &file_api_v1_user_service_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -660,7 +751,7 @@ func (x *UpdateUserRequest) String() string { ...@@ -660,7 +751,7 @@ func (x *UpdateUserRequest) String() string {
func (*UpdateUserRequest) ProtoMessage() {} func (*UpdateUserRequest) ProtoMessage() {}
func (x *UpdateUserRequest) ProtoReflect() protoreflect.Message { func (x *UpdateUserRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[5] mi := &file_api_v1_user_service_proto_msgTypes[7]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -673,7 +764,7 @@ func (x *UpdateUserRequest) ProtoReflect() protoreflect.Message { ...@@ -673,7 +764,7 @@ func (x *UpdateUserRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateUserRequest.ProtoReflect.Descriptor instead. // Deprecated: Use UpdateUserRequest.ProtoReflect.Descriptor instead.
func (*UpdateUserRequest) Descriptor() ([]byte, []int) { func (*UpdateUserRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{5} return file_api_v1_user_service_proto_rawDescGZIP(), []int{7}
} }
func (x *UpdateUserRequest) GetUser() *User { func (x *UpdateUserRequest) GetUser() *User {
...@@ -710,7 +801,7 @@ type DeleteUserRequest struct { ...@@ -710,7 +801,7 @@ type DeleteUserRequest struct {
func (x *DeleteUserRequest) Reset() { func (x *DeleteUserRequest) Reset() {
*x = DeleteUserRequest{} *x = DeleteUserRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[6] mi := &file_api_v1_user_service_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -722,7 +813,7 @@ func (x *DeleteUserRequest) String() string { ...@@ -722,7 +813,7 @@ func (x *DeleteUserRequest) String() string {
func (*DeleteUserRequest) ProtoMessage() {} func (*DeleteUserRequest) ProtoMessage() {}
func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message { func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[6] mi := &file_api_v1_user_service_proto_msgTypes[8]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -735,7 +826,7 @@ func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message { ...@@ -735,7 +826,7 @@ func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeleteUserRequest.ProtoReflect.Descriptor instead. // Deprecated: Use DeleteUserRequest.ProtoReflect.Descriptor instead.
func (*DeleteUserRequest) Descriptor() ([]byte, []int) { func (*DeleteUserRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{6} return file_api_v1_user_service_proto_rawDescGZIP(), []int{8}
} }
func (x *DeleteUserRequest) GetName() string { func (x *DeleteUserRequest) GetName() string {
...@@ -774,7 +865,7 @@ type UserStats struct { ...@@ -774,7 +865,7 @@ type UserStats struct {
func (x *UserStats) Reset() { func (x *UserStats) Reset() {
*x = UserStats{} *x = UserStats{}
mi := &file_api_v1_user_service_proto_msgTypes[7] mi := &file_api_v1_user_service_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -786,7 +877,7 @@ func (x *UserStats) String() string { ...@@ -786,7 +877,7 @@ func (x *UserStats) String() string {
func (*UserStats) ProtoMessage() {} func (*UserStats) ProtoMessage() {}
func (x *UserStats) ProtoReflect() protoreflect.Message { func (x *UserStats) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[7] mi := &file_api_v1_user_service_proto_msgTypes[9]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -799,7 +890,7 @@ func (x *UserStats) ProtoReflect() protoreflect.Message { ...@@ -799,7 +890,7 @@ func (x *UserStats) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserStats.ProtoReflect.Descriptor instead. // Deprecated: Use UserStats.ProtoReflect.Descriptor instead.
func (*UserStats) Descriptor() ([]byte, []int) { func (*UserStats) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{7} return file_api_v1_user_service_proto_rawDescGZIP(), []int{9}
} }
func (x *UserStats) GetName() string { func (x *UserStats) GetName() string {
...@@ -855,7 +946,7 @@ type GetUserStatsRequest struct { ...@@ -855,7 +946,7 @@ type GetUserStatsRequest struct {
func (x *GetUserStatsRequest) Reset() { func (x *GetUserStatsRequest) Reset() {
*x = GetUserStatsRequest{} *x = GetUserStatsRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[8] mi := &file_api_v1_user_service_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -867,7 +958,7 @@ func (x *GetUserStatsRequest) String() string { ...@@ -867,7 +958,7 @@ func (x *GetUserStatsRequest) String() string {
func (*GetUserStatsRequest) ProtoMessage() {} func (*GetUserStatsRequest) ProtoMessage() {}
func (x *GetUserStatsRequest) ProtoReflect() protoreflect.Message { func (x *GetUserStatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[8] mi := &file_api_v1_user_service_proto_msgTypes[10]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -880,7 +971,7 @@ func (x *GetUserStatsRequest) ProtoReflect() protoreflect.Message { ...@@ -880,7 +971,7 @@ func (x *GetUserStatsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetUserStatsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetUserStatsRequest.ProtoReflect.Descriptor instead.
func (*GetUserStatsRequest) Descriptor() ([]byte, []int) { func (*GetUserStatsRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{8} return file_api_v1_user_service_proto_rawDescGZIP(), []int{10}
} }
func (x *GetUserStatsRequest) GetName() string { func (x *GetUserStatsRequest) GetName() string {
...@@ -898,7 +989,7 @@ type ListAllUserStatsRequest struct { ...@@ -898,7 +989,7 @@ type ListAllUserStatsRequest struct {
func (x *ListAllUserStatsRequest) Reset() { func (x *ListAllUserStatsRequest) Reset() {
*x = ListAllUserStatsRequest{} *x = ListAllUserStatsRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[9] mi := &file_api_v1_user_service_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -910,7 +1001,7 @@ func (x *ListAllUserStatsRequest) String() string { ...@@ -910,7 +1001,7 @@ func (x *ListAllUserStatsRequest) String() string {
func (*ListAllUserStatsRequest) ProtoMessage() {} func (*ListAllUserStatsRequest) ProtoMessage() {}
func (x *ListAllUserStatsRequest) ProtoReflect() protoreflect.Message { func (x *ListAllUserStatsRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[9] mi := &file_api_v1_user_service_proto_msgTypes[11]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -923,7 +1014,7 @@ func (x *ListAllUserStatsRequest) ProtoReflect() protoreflect.Message { ...@@ -923,7 +1014,7 @@ func (x *ListAllUserStatsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListAllUserStatsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ListAllUserStatsRequest.ProtoReflect.Descriptor instead.
func (*ListAllUserStatsRequest) Descriptor() ([]byte, []int) { func (*ListAllUserStatsRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{9} return file_api_v1_user_service_proto_rawDescGZIP(), []int{11}
} }
type ListAllUserStatsResponse struct { type ListAllUserStatsResponse struct {
...@@ -936,7 +1027,7 @@ type ListAllUserStatsResponse struct { ...@@ -936,7 +1027,7 @@ type ListAllUserStatsResponse struct {
func (x *ListAllUserStatsResponse) Reset() { func (x *ListAllUserStatsResponse) Reset() {
*x = ListAllUserStatsResponse{} *x = ListAllUserStatsResponse{}
mi := &file_api_v1_user_service_proto_msgTypes[10] mi := &file_api_v1_user_service_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -948,7 +1039,7 @@ func (x *ListAllUserStatsResponse) String() string { ...@@ -948,7 +1039,7 @@ func (x *ListAllUserStatsResponse) String() string {
func (*ListAllUserStatsResponse) ProtoMessage() {} func (*ListAllUserStatsResponse) ProtoMessage() {}
func (x *ListAllUserStatsResponse) ProtoReflect() protoreflect.Message { func (x *ListAllUserStatsResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[10] mi := &file_api_v1_user_service_proto_msgTypes[12]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -961,7 +1052,7 @@ func (x *ListAllUserStatsResponse) ProtoReflect() protoreflect.Message { ...@@ -961,7 +1052,7 @@ func (x *ListAllUserStatsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListAllUserStatsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListAllUserStatsResponse.ProtoReflect.Descriptor instead.
func (*ListAllUserStatsResponse) Descriptor() ([]byte, []int) { func (*ListAllUserStatsResponse) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{10} return file_api_v1_user_service_proto_rawDescGZIP(), []int{12}
} }
func (x *ListAllUserStatsResponse) GetStats() []*UserStats { func (x *ListAllUserStatsResponse) GetStats() []*UserStats {
...@@ -989,7 +1080,7 @@ type UserSetting struct { ...@@ -989,7 +1080,7 @@ type UserSetting struct {
func (x *UserSetting) Reset() { func (x *UserSetting) Reset() {
*x = UserSetting{} *x = UserSetting{}
mi := &file_api_v1_user_service_proto_msgTypes[11] mi := &file_api_v1_user_service_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1001,7 +1092,7 @@ func (x *UserSetting) String() string { ...@@ -1001,7 +1092,7 @@ func (x *UserSetting) String() string {
func (*UserSetting) ProtoMessage() {} func (*UserSetting) ProtoMessage() {}
func (x *UserSetting) ProtoReflect() protoreflect.Message { func (x *UserSetting) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[11] mi := &file_api_v1_user_service_proto_msgTypes[13]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1014,7 +1105,7 @@ func (x *UserSetting) ProtoReflect() protoreflect.Message { ...@@ -1014,7 +1105,7 @@ func (x *UserSetting) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserSetting.ProtoReflect.Descriptor instead. // Deprecated: Use UserSetting.ProtoReflect.Descriptor instead.
func (*UserSetting) Descriptor() ([]byte, []int) { func (*UserSetting) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{11} return file_api_v1_user_service_proto_rawDescGZIP(), []int{13}
} }
func (x *UserSetting) GetName() string { func (x *UserSetting) GetName() string {
...@@ -1076,7 +1167,7 @@ type GetUserSettingRequest struct { ...@@ -1076,7 +1167,7 @@ type GetUserSettingRequest struct {
func (x *GetUserSettingRequest) Reset() { func (x *GetUserSettingRequest) Reset() {
*x = GetUserSettingRequest{} *x = GetUserSettingRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[12] mi := &file_api_v1_user_service_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1088,7 +1179,7 @@ func (x *GetUserSettingRequest) String() string { ...@@ -1088,7 +1179,7 @@ func (x *GetUserSettingRequest) String() string {
func (*GetUserSettingRequest) ProtoMessage() {} func (*GetUserSettingRequest) ProtoMessage() {}
func (x *GetUserSettingRequest) ProtoReflect() protoreflect.Message { func (x *GetUserSettingRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[12] mi := &file_api_v1_user_service_proto_msgTypes[14]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1101,7 +1192,7 @@ func (x *GetUserSettingRequest) ProtoReflect() protoreflect.Message { ...@@ -1101,7 +1192,7 @@ func (x *GetUserSettingRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetUserSettingRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetUserSettingRequest.ProtoReflect.Descriptor instead.
func (*GetUserSettingRequest) Descriptor() ([]byte, []int) { func (*GetUserSettingRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{12} return file_api_v1_user_service_proto_rawDescGZIP(), []int{14}
} }
func (x *GetUserSettingRequest) GetName() string { func (x *GetUserSettingRequest) GetName() string {
...@@ -1123,7 +1214,7 @@ type UpdateUserSettingRequest struct { ...@@ -1123,7 +1214,7 @@ type UpdateUserSettingRequest struct {
func (x *UpdateUserSettingRequest) Reset() { func (x *UpdateUserSettingRequest) Reset() {
*x = UpdateUserSettingRequest{} *x = UpdateUserSettingRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[13] mi := &file_api_v1_user_service_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1135,7 +1226,7 @@ func (x *UpdateUserSettingRequest) String() string { ...@@ -1135,7 +1226,7 @@ func (x *UpdateUserSettingRequest) String() string {
func (*UpdateUserSettingRequest) ProtoMessage() {} func (*UpdateUserSettingRequest) ProtoMessage() {}
func (x *UpdateUserSettingRequest) ProtoReflect() protoreflect.Message { func (x *UpdateUserSettingRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[13] mi := &file_api_v1_user_service_proto_msgTypes[15]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1148,7 +1239,7 @@ func (x *UpdateUserSettingRequest) ProtoReflect() protoreflect.Message { ...@@ -1148,7 +1239,7 @@ func (x *UpdateUserSettingRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateUserSettingRequest.ProtoReflect.Descriptor instead. // Deprecated: Use UpdateUserSettingRequest.ProtoReflect.Descriptor instead.
func (*UpdateUserSettingRequest) Descriptor() ([]byte, []int) { func (*UpdateUserSettingRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{13} return file_api_v1_user_service_proto_rawDescGZIP(), []int{15}
} }
func (x *UpdateUserSettingRequest) GetSetting() *UserSetting { func (x *UpdateUserSettingRequest) GetSetting() *UserSetting {
...@@ -1185,7 +1276,7 @@ type ListUserSettingsRequest struct { ...@@ -1185,7 +1276,7 @@ type ListUserSettingsRequest struct {
func (x *ListUserSettingsRequest) Reset() { func (x *ListUserSettingsRequest) Reset() {
*x = ListUserSettingsRequest{} *x = ListUserSettingsRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[14] mi := &file_api_v1_user_service_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1197,7 +1288,7 @@ func (x *ListUserSettingsRequest) String() string { ...@@ -1197,7 +1288,7 @@ func (x *ListUserSettingsRequest) String() string {
func (*ListUserSettingsRequest) ProtoMessage() {} func (*ListUserSettingsRequest) ProtoMessage() {}
func (x *ListUserSettingsRequest) ProtoReflect() protoreflect.Message { func (x *ListUserSettingsRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[14] mi := &file_api_v1_user_service_proto_msgTypes[16]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1210,7 +1301,7 @@ func (x *ListUserSettingsRequest) ProtoReflect() protoreflect.Message { ...@@ -1210,7 +1301,7 @@ func (x *ListUserSettingsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListUserSettingsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ListUserSettingsRequest.ProtoReflect.Descriptor instead.
func (*ListUserSettingsRequest) Descriptor() ([]byte, []int) { func (*ListUserSettingsRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{14} return file_api_v1_user_service_proto_rawDescGZIP(), []int{16}
} }
func (x *ListUserSettingsRequest) GetParent() string { func (x *ListUserSettingsRequest) GetParent() string {
...@@ -1250,7 +1341,7 @@ type ListUserSettingsResponse struct { ...@@ -1250,7 +1341,7 @@ type ListUserSettingsResponse struct {
func (x *ListUserSettingsResponse) Reset() { func (x *ListUserSettingsResponse) Reset() {
*x = ListUserSettingsResponse{} *x = ListUserSettingsResponse{}
mi := &file_api_v1_user_service_proto_msgTypes[15] mi := &file_api_v1_user_service_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1262,7 +1353,7 @@ func (x *ListUserSettingsResponse) String() string { ...@@ -1262,7 +1353,7 @@ func (x *ListUserSettingsResponse) String() string {
func (*ListUserSettingsResponse) ProtoMessage() {} func (*ListUserSettingsResponse) ProtoMessage() {}
func (x *ListUserSettingsResponse) ProtoReflect() protoreflect.Message { func (x *ListUserSettingsResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[15] mi := &file_api_v1_user_service_proto_msgTypes[17]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1275,7 +1366,7 @@ func (x *ListUserSettingsResponse) ProtoReflect() protoreflect.Message { ...@@ -1275,7 +1366,7 @@ func (x *ListUserSettingsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListUserSettingsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListUserSettingsResponse.ProtoReflect.Descriptor instead.
func (*ListUserSettingsResponse) Descriptor() ([]byte, []int) { func (*ListUserSettingsResponse) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{15} return file_api_v1_user_service_proto_rawDescGZIP(), []int{17}
} }
func (x *ListUserSettingsResponse) GetSettings() []*UserSetting { func (x *ListUserSettingsResponse) GetSettings() []*UserSetting {
...@@ -1320,7 +1411,7 @@ type PersonalAccessToken struct { ...@@ -1320,7 +1411,7 @@ type PersonalAccessToken struct {
func (x *PersonalAccessToken) Reset() { func (x *PersonalAccessToken) Reset() {
*x = PersonalAccessToken{} *x = PersonalAccessToken{}
mi := &file_api_v1_user_service_proto_msgTypes[16] mi := &file_api_v1_user_service_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1332,7 +1423,7 @@ func (x *PersonalAccessToken) String() string { ...@@ -1332,7 +1423,7 @@ func (x *PersonalAccessToken) String() string {
func (*PersonalAccessToken) ProtoMessage() {} func (*PersonalAccessToken) ProtoMessage() {}
func (x *PersonalAccessToken) ProtoReflect() protoreflect.Message { func (x *PersonalAccessToken) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[16] mi := &file_api_v1_user_service_proto_msgTypes[18]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1345,7 +1436,7 @@ func (x *PersonalAccessToken) ProtoReflect() protoreflect.Message { ...@@ -1345,7 +1436,7 @@ func (x *PersonalAccessToken) ProtoReflect() protoreflect.Message {
// Deprecated: Use PersonalAccessToken.ProtoReflect.Descriptor instead. // Deprecated: Use PersonalAccessToken.ProtoReflect.Descriptor instead.
func (*PersonalAccessToken) Descriptor() ([]byte, []int) { func (*PersonalAccessToken) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{16} return file_api_v1_user_service_proto_rawDescGZIP(), []int{18}
} }
func (x *PersonalAccessToken) GetName() string { func (x *PersonalAccessToken) GetName() string {
...@@ -1398,7 +1489,7 @@ type ListPersonalAccessTokensRequest struct { ...@@ -1398,7 +1489,7 @@ type ListPersonalAccessTokensRequest struct {
func (x *ListPersonalAccessTokensRequest) Reset() { func (x *ListPersonalAccessTokensRequest) Reset() {
*x = ListPersonalAccessTokensRequest{} *x = ListPersonalAccessTokensRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[17] mi := &file_api_v1_user_service_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1410,7 +1501,7 @@ func (x *ListPersonalAccessTokensRequest) String() string { ...@@ -1410,7 +1501,7 @@ func (x *ListPersonalAccessTokensRequest) String() string {
func (*ListPersonalAccessTokensRequest) ProtoMessage() {} func (*ListPersonalAccessTokensRequest) ProtoMessage() {}
func (x *ListPersonalAccessTokensRequest) ProtoReflect() protoreflect.Message { func (x *ListPersonalAccessTokensRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[17] mi := &file_api_v1_user_service_proto_msgTypes[19]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1423,7 +1514,7 @@ func (x *ListPersonalAccessTokensRequest) ProtoReflect() protoreflect.Message { ...@@ -1423,7 +1514,7 @@ func (x *ListPersonalAccessTokensRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListPersonalAccessTokensRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ListPersonalAccessTokensRequest.ProtoReflect.Descriptor instead.
func (*ListPersonalAccessTokensRequest) Descriptor() ([]byte, []int) { func (*ListPersonalAccessTokensRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{17} return file_api_v1_user_service_proto_rawDescGZIP(), []int{19}
} }
func (x *ListPersonalAccessTokensRequest) GetParent() string { func (x *ListPersonalAccessTokensRequest) GetParent() string {
...@@ -1461,7 +1552,7 @@ type ListPersonalAccessTokensResponse struct { ...@@ -1461,7 +1552,7 @@ type ListPersonalAccessTokensResponse struct {
func (x *ListPersonalAccessTokensResponse) Reset() { func (x *ListPersonalAccessTokensResponse) Reset() {
*x = ListPersonalAccessTokensResponse{} *x = ListPersonalAccessTokensResponse{}
mi := &file_api_v1_user_service_proto_msgTypes[18] mi := &file_api_v1_user_service_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1473,7 +1564,7 @@ func (x *ListPersonalAccessTokensResponse) String() string { ...@@ -1473,7 +1564,7 @@ func (x *ListPersonalAccessTokensResponse) String() string {
func (*ListPersonalAccessTokensResponse) ProtoMessage() {} func (*ListPersonalAccessTokensResponse) ProtoMessage() {}
func (x *ListPersonalAccessTokensResponse) ProtoReflect() protoreflect.Message { func (x *ListPersonalAccessTokensResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[18] mi := &file_api_v1_user_service_proto_msgTypes[20]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1486,7 +1577,7 @@ func (x *ListPersonalAccessTokensResponse) ProtoReflect() protoreflect.Message { ...@@ -1486,7 +1577,7 @@ func (x *ListPersonalAccessTokensResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListPersonalAccessTokensResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListPersonalAccessTokensResponse.ProtoReflect.Descriptor instead.
func (*ListPersonalAccessTokensResponse) Descriptor() ([]byte, []int) { func (*ListPersonalAccessTokensResponse) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{18} return file_api_v1_user_service_proto_rawDescGZIP(), []int{20}
} }
func (x *ListPersonalAccessTokensResponse) GetPersonalAccessTokens() []*PersonalAccessToken { func (x *ListPersonalAccessTokensResponse) GetPersonalAccessTokens() []*PersonalAccessToken {
...@@ -1525,7 +1616,7 @@ type CreatePersonalAccessTokenRequest struct { ...@@ -1525,7 +1616,7 @@ type CreatePersonalAccessTokenRequest struct {
func (x *CreatePersonalAccessTokenRequest) Reset() { func (x *CreatePersonalAccessTokenRequest) Reset() {
*x = CreatePersonalAccessTokenRequest{} *x = CreatePersonalAccessTokenRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[19] mi := &file_api_v1_user_service_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1537,7 +1628,7 @@ func (x *CreatePersonalAccessTokenRequest) String() string { ...@@ -1537,7 +1628,7 @@ func (x *CreatePersonalAccessTokenRequest) String() string {
func (*CreatePersonalAccessTokenRequest) ProtoMessage() {} func (*CreatePersonalAccessTokenRequest) ProtoMessage() {}
func (x *CreatePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message { func (x *CreatePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[19] mi := &file_api_v1_user_service_proto_msgTypes[21]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1550,7 +1641,7 @@ func (x *CreatePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message { ...@@ -1550,7 +1641,7 @@ func (x *CreatePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreatePersonalAccessTokenRequest.ProtoReflect.Descriptor instead. // Deprecated: Use CreatePersonalAccessTokenRequest.ProtoReflect.Descriptor instead.
func (*CreatePersonalAccessTokenRequest) Descriptor() ([]byte, []int) { func (*CreatePersonalAccessTokenRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{19} return file_api_v1_user_service_proto_rawDescGZIP(), []int{21}
} }
func (x *CreatePersonalAccessTokenRequest) GetParent() string { func (x *CreatePersonalAccessTokenRequest) GetParent() string {
...@@ -1587,7 +1678,7 @@ type CreatePersonalAccessTokenResponse struct { ...@@ -1587,7 +1678,7 @@ type CreatePersonalAccessTokenResponse struct {
func (x *CreatePersonalAccessTokenResponse) Reset() { func (x *CreatePersonalAccessTokenResponse) Reset() {
*x = CreatePersonalAccessTokenResponse{} *x = CreatePersonalAccessTokenResponse{}
mi := &file_api_v1_user_service_proto_msgTypes[20] mi := &file_api_v1_user_service_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1599,7 +1690,7 @@ func (x *CreatePersonalAccessTokenResponse) String() string { ...@@ -1599,7 +1690,7 @@ func (x *CreatePersonalAccessTokenResponse) String() string {
func (*CreatePersonalAccessTokenResponse) ProtoMessage() {} func (*CreatePersonalAccessTokenResponse) ProtoMessage() {}
func (x *CreatePersonalAccessTokenResponse) ProtoReflect() protoreflect.Message { func (x *CreatePersonalAccessTokenResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[20] mi := &file_api_v1_user_service_proto_msgTypes[22]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1612,7 +1703,7 @@ func (x *CreatePersonalAccessTokenResponse) ProtoReflect() protoreflect.Message ...@@ -1612,7 +1703,7 @@ func (x *CreatePersonalAccessTokenResponse) ProtoReflect() protoreflect.Message
// Deprecated: Use CreatePersonalAccessTokenResponse.ProtoReflect.Descriptor instead. // Deprecated: Use CreatePersonalAccessTokenResponse.ProtoReflect.Descriptor instead.
func (*CreatePersonalAccessTokenResponse) Descriptor() ([]byte, []int) { func (*CreatePersonalAccessTokenResponse) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{20} return file_api_v1_user_service_proto_rawDescGZIP(), []int{22}
} }
func (x *CreatePersonalAccessTokenResponse) GetPersonalAccessToken() *PersonalAccessToken { func (x *CreatePersonalAccessTokenResponse) GetPersonalAccessToken() *PersonalAccessToken {
...@@ -1640,7 +1731,7 @@ type DeletePersonalAccessTokenRequest struct { ...@@ -1640,7 +1731,7 @@ type DeletePersonalAccessTokenRequest struct {
func (x *DeletePersonalAccessTokenRequest) Reset() { func (x *DeletePersonalAccessTokenRequest) Reset() {
*x = DeletePersonalAccessTokenRequest{} *x = DeletePersonalAccessTokenRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[21] mi := &file_api_v1_user_service_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1652,7 +1743,7 @@ func (x *DeletePersonalAccessTokenRequest) String() string { ...@@ -1652,7 +1743,7 @@ func (x *DeletePersonalAccessTokenRequest) String() string {
func (*DeletePersonalAccessTokenRequest) ProtoMessage() {} func (*DeletePersonalAccessTokenRequest) ProtoMessage() {}
func (x *DeletePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message { func (x *DeletePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[21] mi := &file_api_v1_user_service_proto_msgTypes[23]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1665,7 +1756,7 @@ func (x *DeletePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message { ...@@ -1665,7 +1756,7 @@ func (x *DeletePersonalAccessTokenRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeletePersonalAccessTokenRequest.ProtoReflect.Descriptor instead. // Deprecated: Use DeletePersonalAccessTokenRequest.ProtoReflect.Descriptor instead.
func (*DeletePersonalAccessTokenRequest) Descriptor() ([]byte, []int) { func (*DeletePersonalAccessTokenRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{21} return file_api_v1_user_service_proto_rawDescGZIP(), []int{23}
} }
func (x *DeletePersonalAccessTokenRequest) GetName() string { func (x *DeletePersonalAccessTokenRequest) GetName() string {
...@@ -1695,7 +1786,7 @@ type UserWebhook struct { ...@@ -1695,7 +1786,7 @@ type UserWebhook struct {
func (x *UserWebhook) Reset() { func (x *UserWebhook) Reset() {
*x = UserWebhook{} *x = UserWebhook{}
mi := &file_api_v1_user_service_proto_msgTypes[22] mi := &file_api_v1_user_service_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1707,7 +1798,7 @@ func (x *UserWebhook) String() string { ...@@ -1707,7 +1798,7 @@ func (x *UserWebhook) String() string {
func (*UserWebhook) ProtoMessage() {} func (*UserWebhook) ProtoMessage() {}
func (x *UserWebhook) ProtoReflect() protoreflect.Message { func (x *UserWebhook) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[22] mi := &file_api_v1_user_service_proto_msgTypes[24]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1720,7 +1811,7 @@ func (x *UserWebhook) ProtoReflect() protoreflect.Message { ...@@ -1720,7 +1811,7 @@ func (x *UserWebhook) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserWebhook.ProtoReflect.Descriptor instead. // Deprecated: Use UserWebhook.ProtoReflect.Descriptor instead.
func (*UserWebhook) Descriptor() ([]byte, []int) { func (*UserWebhook) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{22} return file_api_v1_user_service_proto_rawDescGZIP(), []int{24}
} }
func (x *UserWebhook) GetName() string { func (x *UserWebhook) GetName() string {
...@@ -1769,7 +1860,7 @@ type ListUserWebhooksRequest struct { ...@@ -1769,7 +1860,7 @@ type ListUserWebhooksRequest struct {
func (x *ListUserWebhooksRequest) Reset() { func (x *ListUserWebhooksRequest) Reset() {
*x = ListUserWebhooksRequest{} *x = ListUserWebhooksRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[23] mi := &file_api_v1_user_service_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1781,7 +1872,7 @@ func (x *ListUserWebhooksRequest) String() string { ...@@ -1781,7 +1872,7 @@ func (x *ListUserWebhooksRequest) String() string {
func (*ListUserWebhooksRequest) ProtoMessage() {} func (*ListUserWebhooksRequest) ProtoMessage() {}
func (x *ListUserWebhooksRequest) ProtoReflect() protoreflect.Message { func (x *ListUserWebhooksRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[23] mi := &file_api_v1_user_service_proto_msgTypes[25]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1794,7 +1885,7 @@ func (x *ListUserWebhooksRequest) ProtoReflect() protoreflect.Message { ...@@ -1794,7 +1885,7 @@ func (x *ListUserWebhooksRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListUserWebhooksRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ListUserWebhooksRequest.ProtoReflect.Descriptor instead.
func (*ListUserWebhooksRequest) Descriptor() ([]byte, []int) { func (*ListUserWebhooksRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{23} return file_api_v1_user_service_proto_rawDescGZIP(), []int{25}
} }
func (x *ListUserWebhooksRequest) GetParent() string { func (x *ListUserWebhooksRequest) GetParent() string {
...@@ -1814,7 +1905,7 @@ type ListUserWebhooksResponse struct { ...@@ -1814,7 +1905,7 @@ type ListUserWebhooksResponse struct {
func (x *ListUserWebhooksResponse) Reset() { func (x *ListUserWebhooksResponse) Reset() {
*x = ListUserWebhooksResponse{} *x = ListUserWebhooksResponse{}
mi := &file_api_v1_user_service_proto_msgTypes[24] mi := &file_api_v1_user_service_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1826,7 +1917,7 @@ func (x *ListUserWebhooksResponse) String() string { ...@@ -1826,7 +1917,7 @@ func (x *ListUserWebhooksResponse) String() string {
func (*ListUserWebhooksResponse) ProtoMessage() {} func (*ListUserWebhooksResponse) ProtoMessage() {}
func (x *ListUserWebhooksResponse) ProtoReflect() protoreflect.Message { func (x *ListUserWebhooksResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[24] mi := &file_api_v1_user_service_proto_msgTypes[26]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1839,7 +1930,7 @@ func (x *ListUserWebhooksResponse) ProtoReflect() protoreflect.Message { ...@@ -1839,7 +1930,7 @@ func (x *ListUserWebhooksResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListUserWebhooksResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListUserWebhooksResponse.ProtoReflect.Descriptor instead.
func (*ListUserWebhooksResponse) Descriptor() ([]byte, []int) { func (*ListUserWebhooksResponse) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{24} return file_api_v1_user_service_proto_rawDescGZIP(), []int{26}
} }
func (x *ListUserWebhooksResponse) GetWebhooks() []*UserWebhook { func (x *ListUserWebhooksResponse) GetWebhooks() []*UserWebhook {
...@@ -1862,7 +1953,7 @@ type CreateUserWebhookRequest struct { ...@@ -1862,7 +1953,7 @@ type CreateUserWebhookRequest struct {
func (x *CreateUserWebhookRequest) Reset() { func (x *CreateUserWebhookRequest) Reset() {
*x = CreateUserWebhookRequest{} *x = CreateUserWebhookRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[25] mi := &file_api_v1_user_service_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1874,7 +1965,7 @@ func (x *CreateUserWebhookRequest) String() string { ...@@ -1874,7 +1965,7 @@ func (x *CreateUserWebhookRequest) String() string {
func (*CreateUserWebhookRequest) ProtoMessage() {} func (*CreateUserWebhookRequest) ProtoMessage() {}
func (x *CreateUserWebhookRequest) ProtoReflect() protoreflect.Message { func (x *CreateUserWebhookRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[25] mi := &file_api_v1_user_service_proto_msgTypes[27]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1887,7 +1978,7 @@ func (x *CreateUserWebhookRequest) ProtoReflect() protoreflect.Message { ...@@ -1887,7 +1978,7 @@ func (x *CreateUserWebhookRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateUserWebhookRequest.ProtoReflect.Descriptor instead. // Deprecated: Use CreateUserWebhookRequest.ProtoReflect.Descriptor instead.
func (*CreateUserWebhookRequest) Descriptor() ([]byte, []int) { func (*CreateUserWebhookRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{25} return file_api_v1_user_service_proto_rawDescGZIP(), []int{27}
} }
func (x *CreateUserWebhookRequest) GetParent() string { func (x *CreateUserWebhookRequest) GetParent() string {
...@@ -1916,7 +2007,7 @@ type UpdateUserWebhookRequest struct { ...@@ -1916,7 +2007,7 @@ type UpdateUserWebhookRequest struct {
func (x *UpdateUserWebhookRequest) Reset() { func (x *UpdateUserWebhookRequest) Reset() {
*x = UpdateUserWebhookRequest{} *x = UpdateUserWebhookRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[26] mi := &file_api_v1_user_service_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1928,7 +2019,7 @@ func (x *UpdateUserWebhookRequest) String() string { ...@@ -1928,7 +2019,7 @@ func (x *UpdateUserWebhookRequest) String() string {
func (*UpdateUserWebhookRequest) ProtoMessage() {} func (*UpdateUserWebhookRequest) ProtoMessage() {}
func (x *UpdateUserWebhookRequest) ProtoReflect() protoreflect.Message { func (x *UpdateUserWebhookRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[26] mi := &file_api_v1_user_service_proto_msgTypes[28]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1941,7 +2032,7 @@ func (x *UpdateUserWebhookRequest) ProtoReflect() protoreflect.Message { ...@@ -1941,7 +2032,7 @@ func (x *UpdateUserWebhookRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateUserWebhookRequest.ProtoReflect.Descriptor instead. // Deprecated: Use UpdateUserWebhookRequest.ProtoReflect.Descriptor instead.
func (*UpdateUserWebhookRequest) Descriptor() ([]byte, []int) { func (*UpdateUserWebhookRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{26} return file_api_v1_user_service_proto_rawDescGZIP(), []int{28}
} }
func (x *UpdateUserWebhookRequest) GetWebhook() *UserWebhook { func (x *UpdateUserWebhookRequest) GetWebhook() *UserWebhook {
...@@ -1969,7 +2060,7 @@ type DeleteUserWebhookRequest struct { ...@@ -1969,7 +2060,7 @@ type DeleteUserWebhookRequest struct {
func (x *DeleteUserWebhookRequest) Reset() { func (x *DeleteUserWebhookRequest) Reset() {
*x = DeleteUserWebhookRequest{} *x = DeleteUserWebhookRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[27] mi := &file_api_v1_user_service_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -1981,7 +2072,7 @@ func (x *DeleteUserWebhookRequest) String() string { ...@@ -1981,7 +2072,7 @@ func (x *DeleteUserWebhookRequest) String() string {
func (*DeleteUserWebhookRequest) ProtoMessage() {} func (*DeleteUserWebhookRequest) ProtoMessage() {}
func (x *DeleteUserWebhookRequest) ProtoReflect() protoreflect.Message { func (x *DeleteUserWebhookRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[27] mi := &file_api_v1_user_service_proto_msgTypes[29]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -1994,7 +2085,7 @@ func (x *DeleteUserWebhookRequest) ProtoReflect() protoreflect.Message { ...@@ -1994,7 +2085,7 @@ func (x *DeleteUserWebhookRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeleteUserWebhookRequest.ProtoReflect.Descriptor instead. // Deprecated: Use DeleteUserWebhookRequest.ProtoReflect.Descriptor instead.
func (*DeleteUserWebhookRequest) Descriptor() ([]byte, []int) { func (*DeleteUserWebhookRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{27} return file_api_v1_user_service_proto_rawDescGZIP(), []int{29}
} }
func (x *DeleteUserWebhookRequest) GetName() string { func (x *DeleteUserWebhookRequest) GetName() string {
...@@ -2012,6 +2103,8 @@ type UserNotification struct { ...@@ -2012,6 +2103,8 @@ type UserNotification struct {
// The sender of the notification. // The sender of the notification.
// Format: users/{user} // Format: users/{user}
Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
// The sender user details.
SenderUser *User `protobuf:"bytes,8,opt,name=sender_user,json=senderUser,proto3" json:"sender_user,omitempty"`
// The status of the notification. // The status of the notification.
Status UserNotification_Status `protobuf:"varint,3,opt,name=status,proto3,enum=memos.api.v1.UserNotification_Status" json:"status,omitempty"` Status UserNotification_Status `protobuf:"varint,3,opt,name=status,proto3,enum=memos.api.v1.UserNotification_Status" json:"status,omitempty"`
// The creation timestamp. // The creation timestamp.
...@@ -2021,6 +2114,7 @@ type UserNotification struct { ...@@ -2021,6 +2114,7 @@ type UserNotification struct {
// Types that are valid to be assigned to Payload: // Types that are valid to be assigned to Payload:
// //
// *UserNotification_MemoComment // *UserNotification_MemoComment
// *UserNotification_MemoMention
Payload isUserNotification_Payload `protobuf_oneof:"payload"` Payload isUserNotification_Payload `protobuf_oneof:"payload"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
...@@ -2028,7 +2122,7 @@ type UserNotification struct { ...@@ -2028,7 +2122,7 @@ type UserNotification struct {
func (x *UserNotification) Reset() { func (x *UserNotification) Reset() {
*x = UserNotification{} *x = UserNotification{}
mi := &file_api_v1_user_service_proto_msgTypes[28] mi := &file_api_v1_user_service_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -2040,7 +2134,7 @@ func (x *UserNotification) String() string { ...@@ -2040,7 +2134,7 @@ func (x *UserNotification) String() string {
func (*UserNotification) ProtoMessage() {} func (*UserNotification) ProtoMessage() {}
func (x *UserNotification) ProtoReflect() protoreflect.Message { func (x *UserNotification) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[28] mi := &file_api_v1_user_service_proto_msgTypes[30]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -2053,7 +2147,7 @@ func (x *UserNotification) ProtoReflect() protoreflect.Message { ...@@ -2053,7 +2147,7 @@ func (x *UserNotification) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserNotification.ProtoReflect.Descriptor instead. // Deprecated: Use UserNotification.ProtoReflect.Descriptor instead.
func (*UserNotification) Descriptor() ([]byte, []int) { func (*UserNotification) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{28} return file_api_v1_user_service_proto_rawDescGZIP(), []int{30}
} }
func (x *UserNotification) GetName() string { func (x *UserNotification) GetName() string {
...@@ -2070,6 +2164,13 @@ func (x *UserNotification) GetSender() string { ...@@ -2070,6 +2164,13 @@ func (x *UserNotification) GetSender() string {
return "" return ""
} }
func (x *UserNotification) GetSenderUser() *User {
if x != nil {
return x.SenderUser
}
return nil
}
func (x *UserNotification) GetStatus() UserNotification_Status { func (x *UserNotification) GetStatus() UserNotification_Status {
if x != nil { if x != nil {
return x.Status return x.Status
...@@ -2107,6 +2208,15 @@ func (x *UserNotification) GetMemoComment() *UserNotification_MemoCommentPayload ...@@ -2107,6 +2208,15 @@ func (x *UserNotification) GetMemoComment() *UserNotification_MemoCommentPayload
return nil return nil
} }
func (x *UserNotification) GetMemoMention() *UserNotification_MemoMentionPayload {
if x != nil {
if x, ok := x.Payload.(*UserNotification_MemoMention); ok {
return x.MemoMention
}
}
return nil
}
type isUserNotification_Payload interface { type isUserNotification_Payload interface {
isUserNotification_Payload() isUserNotification_Payload()
} }
...@@ -2115,8 +2225,14 @@ type UserNotification_MemoComment struct { ...@@ -2115,8 +2225,14 @@ type UserNotification_MemoComment struct {
MemoComment *UserNotification_MemoCommentPayload `protobuf:"bytes,6,opt,name=memo_comment,json=memoComment,proto3,oneof"` MemoComment *UserNotification_MemoCommentPayload `protobuf:"bytes,6,opt,name=memo_comment,json=memoComment,proto3,oneof"`
} }
type UserNotification_MemoMention struct {
MemoMention *UserNotification_MemoMentionPayload `protobuf:"bytes,7,opt,name=memo_mention,json=memoMention,proto3,oneof"`
}
func (*UserNotification_MemoComment) isUserNotification_Payload() {} func (*UserNotification_MemoComment) isUserNotification_Payload() {}
func (*UserNotification_MemoMention) isUserNotification_Payload() {}
type ListUserNotificationsRequest struct { type ListUserNotificationsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
// The parent user resource. // The parent user resource.
...@@ -2131,7 +2247,7 @@ type ListUserNotificationsRequest struct { ...@@ -2131,7 +2247,7 @@ type ListUserNotificationsRequest struct {
func (x *ListUserNotificationsRequest) Reset() { func (x *ListUserNotificationsRequest) Reset() {
*x = ListUserNotificationsRequest{} *x = ListUserNotificationsRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[29] mi := &file_api_v1_user_service_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -2143,7 +2259,7 @@ func (x *ListUserNotificationsRequest) String() string { ...@@ -2143,7 +2259,7 @@ func (x *ListUserNotificationsRequest) String() string {
func (*ListUserNotificationsRequest) ProtoMessage() {} func (*ListUserNotificationsRequest) ProtoMessage() {}
func (x *ListUserNotificationsRequest) ProtoReflect() protoreflect.Message { func (x *ListUserNotificationsRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[29] mi := &file_api_v1_user_service_proto_msgTypes[31]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -2156,7 +2272,7 @@ func (x *ListUserNotificationsRequest) ProtoReflect() protoreflect.Message { ...@@ -2156,7 +2272,7 @@ func (x *ListUserNotificationsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListUserNotificationsRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ListUserNotificationsRequest.ProtoReflect.Descriptor instead.
func (*ListUserNotificationsRequest) Descriptor() ([]byte, []int) { func (*ListUserNotificationsRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{29} return file_api_v1_user_service_proto_rawDescGZIP(), []int{31}
} }
func (x *ListUserNotificationsRequest) GetParent() string { func (x *ListUserNotificationsRequest) GetParent() string {
...@@ -2197,7 +2313,7 @@ type ListUserNotificationsResponse struct { ...@@ -2197,7 +2313,7 @@ type ListUserNotificationsResponse struct {
func (x *ListUserNotificationsResponse) Reset() { func (x *ListUserNotificationsResponse) Reset() {
*x = ListUserNotificationsResponse{} *x = ListUserNotificationsResponse{}
mi := &file_api_v1_user_service_proto_msgTypes[30] mi := &file_api_v1_user_service_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -2209,7 +2325,7 @@ func (x *ListUserNotificationsResponse) String() string { ...@@ -2209,7 +2325,7 @@ func (x *ListUserNotificationsResponse) String() string {
func (*ListUserNotificationsResponse) ProtoMessage() {} func (*ListUserNotificationsResponse) ProtoMessage() {}
func (x *ListUserNotificationsResponse) ProtoReflect() protoreflect.Message { func (x *ListUserNotificationsResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[30] mi := &file_api_v1_user_service_proto_msgTypes[32]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -2222,7 +2338,7 @@ func (x *ListUserNotificationsResponse) ProtoReflect() protoreflect.Message { ...@@ -2222,7 +2338,7 @@ func (x *ListUserNotificationsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListUserNotificationsResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ListUserNotificationsResponse.ProtoReflect.Descriptor instead.
func (*ListUserNotificationsResponse) Descriptor() ([]byte, []int) { func (*ListUserNotificationsResponse) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{30} return file_api_v1_user_service_proto_rawDescGZIP(), []int{32}
} }
func (x *ListUserNotificationsResponse) GetNotifications() []*UserNotification { func (x *ListUserNotificationsResponse) GetNotifications() []*UserNotification {
...@@ -2249,7 +2365,7 @@ type UpdateUserNotificationRequest struct { ...@@ -2249,7 +2365,7 @@ type UpdateUserNotificationRequest struct {
func (x *UpdateUserNotificationRequest) Reset() { func (x *UpdateUserNotificationRequest) Reset() {
*x = UpdateUserNotificationRequest{} *x = UpdateUserNotificationRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[31] mi := &file_api_v1_user_service_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -2261,7 +2377,7 @@ func (x *UpdateUserNotificationRequest) String() string { ...@@ -2261,7 +2377,7 @@ func (x *UpdateUserNotificationRequest) String() string {
func (*UpdateUserNotificationRequest) ProtoMessage() {} func (*UpdateUserNotificationRequest) ProtoMessage() {}
func (x *UpdateUserNotificationRequest) ProtoReflect() protoreflect.Message { func (x *UpdateUserNotificationRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[31] mi := &file_api_v1_user_service_proto_msgTypes[33]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -2274,7 +2390,7 @@ func (x *UpdateUserNotificationRequest) ProtoReflect() protoreflect.Message { ...@@ -2274,7 +2390,7 @@ func (x *UpdateUserNotificationRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateUserNotificationRequest.ProtoReflect.Descriptor instead. // Deprecated: Use UpdateUserNotificationRequest.ProtoReflect.Descriptor instead.
func (*UpdateUserNotificationRequest) Descriptor() ([]byte, []int) { func (*UpdateUserNotificationRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{31} return file_api_v1_user_service_proto_rawDescGZIP(), []int{33}
} }
func (x *UpdateUserNotificationRequest) GetNotification() *UserNotification { func (x *UpdateUserNotificationRequest) GetNotification() *UserNotification {
...@@ -2301,7 +2417,7 @@ type DeleteUserNotificationRequest struct { ...@@ -2301,7 +2417,7 @@ type DeleteUserNotificationRequest struct {
func (x *DeleteUserNotificationRequest) Reset() { func (x *DeleteUserNotificationRequest) Reset() {
*x = DeleteUserNotificationRequest{} *x = DeleteUserNotificationRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[32] mi := &file_api_v1_user_service_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -2313,7 +2429,7 @@ func (x *DeleteUserNotificationRequest) String() string { ...@@ -2313,7 +2429,7 @@ func (x *DeleteUserNotificationRequest) String() string {
func (*DeleteUserNotificationRequest) ProtoMessage() {} func (*DeleteUserNotificationRequest) ProtoMessage() {}
func (x *DeleteUserNotificationRequest) ProtoReflect() protoreflect.Message { func (x *DeleteUserNotificationRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[32] mi := &file_api_v1_user_service_proto_msgTypes[34]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -2326,7 +2442,7 @@ func (x *DeleteUserNotificationRequest) ProtoReflect() protoreflect.Message { ...@@ -2326,7 +2442,7 @@ func (x *DeleteUserNotificationRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeleteUserNotificationRequest.ProtoReflect.Descriptor instead. // Deprecated: Use DeleteUserNotificationRequest.ProtoReflect.Descriptor instead.
func (*DeleteUserNotificationRequest) Descriptor() ([]byte, []int) { func (*DeleteUserNotificationRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{32} return file_api_v1_user_service_proto_rawDescGZIP(), []int{34}
} }
func (x *DeleteUserNotificationRequest) GetName() string { func (x *DeleteUserNotificationRequest) GetName() string {
...@@ -2349,7 +2465,7 @@ type UserStats_MemoTypeStats struct { ...@@ -2349,7 +2465,7 @@ type UserStats_MemoTypeStats struct {
func (x *UserStats_MemoTypeStats) Reset() { func (x *UserStats_MemoTypeStats) Reset() {
*x = UserStats_MemoTypeStats{} *x = UserStats_MemoTypeStats{}
mi := &file_api_v1_user_service_proto_msgTypes[34] mi := &file_api_v1_user_service_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -2361,7 +2477,7 @@ func (x *UserStats_MemoTypeStats) String() string { ...@@ -2361,7 +2477,7 @@ func (x *UserStats_MemoTypeStats) String() string {
func (*UserStats_MemoTypeStats) ProtoMessage() {} func (*UserStats_MemoTypeStats) ProtoMessage() {}
func (x *UserStats_MemoTypeStats) ProtoReflect() protoreflect.Message { func (x *UserStats_MemoTypeStats) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[34] mi := &file_api_v1_user_service_proto_msgTypes[36]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -2374,7 +2490,7 @@ func (x *UserStats_MemoTypeStats) ProtoReflect() protoreflect.Message { ...@@ -2374,7 +2490,7 @@ func (x *UserStats_MemoTypeStats) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserStats_MemoTypeStats.ProtoReflect.Descriptor instead. // Deprecated: Use UserStats_MemoTypeStats.ProtoReflect.Descriptor instead.
func (*UserStats_MemoTypeStats) Descriptor() ([]byte, []int) { func (*UserStats_MemoTypeStats) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{7, 1} return file_api_v1_user_service_proto_rawDescGZIP(), []int{9, 1}
} }
func (x *UserStats_MemoTypeStats) GetLinkCount() int32 { func (x *UserStats_MemoTypeStats) GetLinkCount() int32 {
...@@ -2422,7 +2538,7 @@ type UserSetting_GeneralSetting struct { ...@@ -2422,7 +2538,7 @@ type UserSetting_GeneralSetting struct {
func (x *UserSetting_GeneralSetting) Reset() { func (x *UserSetting_GeneralSetting) Reset() {
*x = UserSetting_GeneralSetting{} *x = UserSetting_GeneralSetting{}
mi := &file_api_v1_user_service_proto_msgTypes[35] mi := &file_api_v1_user_service_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -2434,7 +2550,7 @@ func (x *UserSetting_GeneralSetting) String() string { ...@@ -2434,7 +2550,7 @@ func (x *UserSetting_GeneralSetting) String() string {
func (*UserSetting_GeneralSetting) ProtoMessage() {} func (*UserSetting_GeneralSetting) ProtoMessage() {}
func (x *UserSetting_GeneralSetting) ProtoReflect() protoreflect.Message { func (x *UserSetting_GeneralSetting) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[35] mi := &file_api_v1_user_service_proto_msgTypes[37]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -2447,7 +2563,7 @@ func (x *UserSetting_GeneralSetting) ProtoReflect() protoreflect.Message { ...@@ -2447,7 +2563,7 @@ func (x *UserSetting_GeneralSetting) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserSetting_GeneralSetting.ProtoReflect.Descriptor instead. // Deprecated: Use UserSetting_GeneralSetting.ProtoReflect.Descriptor instead.
func (*UserSetting_GeneralSetting) Descriptor() ([]byte, []int) { func (*UserSetting_GeneralSetting) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{11, 0} return file_api_v1_user_service_proto_rawDescGZIP(), []int{13, 0}
} }
func (x *UserSetting_GeneralSetting) GetLocale() string { func (x *UserSetting_GeneralSetting) GetLocale() string {
...@@ -2482,7 +2598,7 @@ type UserSetting_WebhooksSetting struct { ...@@ -2482,7 +2598,7 @@ type UserSetting_WebhooksSetting struct {
func (x *UserSetting_WebhooksSetting) Reset() { func (x *UserSetting_WebhooksSetting) Reset() {
*x = UserSetting_WebhooksSetting{} *x = UserSetting_WebhooksSetting{}
mi := &file_api_v1_user_service_proto_msgTypes[36] mi := &file_api_v1_user_service_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -2494,7 +2610,7 @@ func (x *UserSetting_WebhooksSetting) String() string { ...@@ -2494,7 +2610,7 @@ func (x *UserSetting_WebhooksSetting) String() string {
func (*UserSetting_WebhooksSetting) ProtoMessage() {} func (*UserSetting_WebhooksSetting) ProtoMessage() {}
func (x *UserSetting_WebhooksSetting) ProtoReflect() protoreflect.Message { func (x *UserSetting_WebhooksSetting) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[36] mi := &file_api_v1_user_service_proto_msgTypes[38]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -2507,7 +2623,7 @@ func (x *UserSetting_WebhooksSetting) ProtoReflect() protoreflect.Message { ...@@ -2507,7 +2623,7 @@ func (x *UserSetting_WebhooksSetting) ProtoReflect() protoreflect.Message {
// Deprecated: Use UserSetting_WebhooksSetting.ProtoReflect.Descriptor instead. // Deprecated: Use UserSetting_WebhooksSetting.ProtoReflect.Descriptor instead.
func (*UserSetting_WebhooksSetting) Descriptor() ([]byte, []int) { func (*UserSetting_WebhooksSetting) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{11, 1} return file_api_v1_user_service_proto_rawDescGZIP(), []int{13, 1}
} }
func (x *UserSetting_WebhooksSetting) GetWebhooks() []*UserWebhook { func (x *UserSetting_WebhooksSetting) GetWebhooks() []*UserWebhook {
...@@ -2525,13 +2641,17 @@ type UserNotification_MemoCommentPayload struct { ...@@ -2525,13 +2641,17 @@ type UserNotification_MemoCommentPayload struct {
// The name of related memo. // The name of related memo.
// Format: memos/{memo} // Format: memos/{memo}
RelatedMemo string `protobuf:"bytes,2,opt,name=related_memo,json=relatedMemo,proto3" json:"related_memo,omitempty"` RelatedMemo string `protobuf:"bytes,2,opt,name=related_memo,json=relatedMemo,proto3" json:"related_memo,omitempty"`
// Preview text of the comment memo.
MemoSnippet string `protobuf:"bytes,3,opt,name=memo_snippet,json=memoSnippet,proto3" json:"memo_snippet,omitempty"`
// Preview text of the related memo.
RelatedMemoSnippet string `protobuf:"bytes,4,opt,name=related_memo_snippet,json=relatedMemoSnippet,proto3" json:"related_memo_snippet,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
func (x *UserNotification_MemoCommentPayload) Reset() { func (x *UserNotification_MemoCommentPayload) Reset() {
*x = UserNotification_MemoCommentPayload{} *x = UserNotification_MemoCommentPayload{}
mi := &file_api_v1_user_service_proto_msgTypes[37] mi := &file_api_v1_user_service_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
...@@ -2543,7 +2663,7 @@ func (x *UserNotification_MemoCommentPayload) String() string { ...@@ -2543,7 +2663,7 @@ func (x *UserNotification_MemoCommentPayload) String() string {
func (*UserNotification_MemoCommentPayload) ProtoMessage() {} func (*UserNotification_MemoCommentPayload) ProtoMessage() {}
func (x *UserNotification_MemoCommentPayload) ProtoReflect() protoreflect.Message { func (x *UserNotification_MemoCommentPayload) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[37] mi := &file_api_v1_user_service_proto_msgTypes[39]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
...@@ -2556,7 +2676,7 @@ func (x *UserNotification_MemoCommentPayload) ProtoReflect() protoreflect.Messag ...@@ -2556,7 +2676,7 @@ func (x *UserNotification_MemoCommentPayload) ProtoReflect() protoreflect.Messag
// Deprecated: Use UserNotification_MemoCommentPayload.ProtoReflect.Descriptor instead. // Deprecated: Use UserNotification_MemoCommentPayload.ProtoReflect.Descriptor instead.
func (*UserNotification_MemoCommentPayload) Descriptor() ([]byte, []int) { func (*UserNotification_MemoCommentPayload) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{28, 0} return file_api_v1_user_service_proto_rawDescGZIP(), []int{30, 0}
} }
func (x *UserNotification_MemoCommentPayload) GetMemo() string { func (x *UserNotification_MemoCommentPayload) GetMemo() string {
...@@ -2573,6 +2693,94 @@ func (x *UserNotification_MemoCommentPayload) GetRelatedMemo() string { ...@@ -2573,6 +2693,94 @@ func (x *UserNotification_MemoCommentPayload) GetRelatedMemo() string {
return "" return ""
} }
func (x *UserNotification_MemoCommentPayload) GetMemoSnippet() string {
if x != nil {
return x.MemoSnippet
}
return ""
}
func (x *UserNotification_MemoCommentPayload) GetRelatedMemoSnippet() string {
if x != nil {
return x.RelatedMemoSnippet
}
return ""
}
type UserNotification_MemoMentionPayload struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The memo that contains the mention.
// Format: memos/{memo}
Memo string `protobuf:"bytes,1,opt,name=memo,proto3" json:"memo,omitempty"`
// The related parent memo when the mention was created in a comment.
// Format: memos/{memo}
RelatedMemo string `protobuf:"bytes,2,opt,name=related_memo,json=relatedMemo,proto3" json:"related_memo,omitempty"`
// Preview text of the memo that contains the mention.
MemoSnippet string `protobuf:"bytes,3,opt,name=memo_snippet,json=memoSnippet,proto3" json:"memo_snippet,omitempty"`
// Preview text of the related parent memo.
RelatedMemoSnippet string `protobuf:"bytes,4,opt,name=related_memo_snippet,json=relatedMemoSnippet,proto3" json:"related_memo_snippet,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UserNotification_MemoMentionPayload) Reset() {
*x = UserNotification_MemoMentionPayload{}
mi := &file_api_v1_user_service_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UserNotification_MemoMentionPayload) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserNotification_MemoMentionPayload) ProtoMessage() {}
func (x *UserNotification_MemoMentionPayload) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[40]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserNotification_MemoMentionPayload.ProtoReflect.Descriptor instead.
func (*UserNotification_MemoMentionPayload) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{30, 1}
}
func (x *UserNotification_MemoMentionPayload) GetMemo() string {
if x != nil {
return x.Memo
}
return ""
}
func (x *UserNotification_MemoMentionPayload) GetRelatedMemo() string {
if x != nil {
return x.RelatedMemo
}
return ""
}
func (x *UserNotification_MemoMentionPayload) GetMemoSnippet() string {
if x != nil {
return x.MemoSnippet
}
return ""
}
func (x *UserNotification_MemoMentionPayload) GetRelatedMemoSnippet() string {
if x != nil {
return x.RelatedMemoSnippet
}
return ""
}
var File_api_v1_user_service_proto protoreflect.FileDescriptor var File_api_v1_user_service_proto protoreflect.FileDescriptor
const file_api_v1_user_service_proto_rawDesc = "" + const file_api_v1_user_service_proto_rawDesc = "" +
...@@ -2609,7 +2817,11 @@ const file_api_v1_user_service_proto_rawDesc = "" + ...@@ -2609,7 +2817,11 @@ const file_api_v1_user_service_proto_rawDesc = "" +
"\x05users\x18\x01 \x03(\v2\x12.memos.api.v1.UserR\x05users\x12&\n" + "\x05users\x18\x01 \x03(\v2\x12.memos.api.v1.UserR\x05users\x12&\n" +
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12\x1d\n" + "\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12\x1d\n" +
"\n" + "\n" +
"total_size\x18\x03 \x01(\x05R\ttotalSize\"}\n" + "total_size\x18\x03 \x01(\x05R\ttotalSize\"4\n" +
"\x14BatchGetUsersRequest\x12\x1c\n" +
"\tusernames\x18\x01 \x03(\tR\tusernames\"A\n" +
"\x15BatchGetUsersResponse\x12(\n" +
"\x05users\x18\x01 \x03(\v2\x12.memos.api.v1.UserR\x05users\"}\n" +
"\x0eGetUserRequest\x12-\n" + "\x0eGetUserRequest\x12-\n" +
"\x04name\x18\x01 \x01(\tB\x19\xe0A\x02\xfaA\x13\n" + "\x04name\x18\x01 \x01(\tB\x19\xe0A\x02\xfaA\x13\n" +
"\x11memos.api.v1/UserR\x04name\x12<\n" + "\x11memos.api.v1/UserR\x04name\x12<\n" +
...@@ -2741,27 +2953,38 @@ const file_api_v1_user_service_proto_rawDesc = "" + ...@@ -2741,27 +2953,38 @@ const file_api_v1_user_service_proto_rawDesc = "" +
"\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskR\n" + "\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskR\n" +
"updateMask\"3\n" + "updateMask\"3\n" +
"\x18DeleteUserWebhookRequest\x12\x17\n" + "\x18DeleteUserWebhookRequest\x12\x17\n" +
"\x04name\x18\x01 \x01(\tB\x03\xe0A\x02R\x04name\"\xb8\x05\n" + "\x04name\x18\x01 \x01(\tB\x03\xe0A\x02R\x04name\"\xda\b\n" +
"\x10UserNotification\x12\x1a\n" + "\x10UserNotification\x12\x1a\n" +
"\x04name\x18\x01 \x01(\tB\x06\xe0A\x03\xe0A\bR\x04name\x121\n" + "\x04name\x18\x01 \x01(\tB\x06\xe0A\x03\xe0A\bR\x04name\x121\n" +
"\x06sender\x18\x02 \x01(\tB\x19\xe0A\x03\xfaA\x13\n" + "\x06sender\x18\x02 \x01(\tB\x19\xe0A\x03\xfaA\x13\n" +
"\x11memos.api.v1/UserR\x06sender\x12B\n" + "\x11memos.api.v1/UserR\x06sender\x128\n" +
"\vsender_user\x18\b \x01(\v2\x12.memos.api.v1.UserB\x03\xe0A\x03R\n" +
"senderUser\x12B\n" +
"\x06status\x18\x03 \x01(\x0e2%.memos.api.v1.UserNotification.StatusB\x03\xe0A\x01R\x06status\x12@\n" + "\x06status\x18\x03 \x01(\x0e2%.memos.api.v1.UserNotification.StatusB\x03\xe0A\x01R\x06status\x12@\n" +
"\vcreate_time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" + "\vcreate_time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" +
"createTime\x12<\n" + "createTime\x12<\n" +
"\x04type\x18\x05 \x01(\x0e2#.memos.api.v1.UserNotification.TypeB\x03\xe0A\x03R\x04type\x12[\n" + "\x04type\x18\x05 \x01(\x0e2#.memos.api.v1.UserNotification.TypeB\x03\xe0A\x03R\x04type\x12[\n" +
"\fmemo_comment\x18\x06 \x01(\v21.memos.api.v1.UserNotification.MemoCommentPayloadB\x03\xe0A\x03H\x00R\vmemoComment\x1aK\n" + "\fmemo_comment\x18\x06 \x01(\v21.memos.api.v1.UserNotification.MemoCommentPayloadB\x03\xe0A\x03H\x00R\vmemoComment\x12[\n" +
"\fmemo_mention\x18\a \x01(\v21.memos.api.v1.UserNotification.MemoMentionPayloadB\x03\xe0A\x03H\x00R\vmemoMention\x1a\xa0\x01\n" +
"\x12MemoCommentPayload\x12\x12\n" + "\x12MemoCommentPayload\x12\x12\n" +
"\x04memo\x18\x01 \x01(\tR\x04memo\x12!\n" + "\x04memo\x18\x01 \x01(\tR\x04memo\x12!\n" +
"\frelated_memo\x18\x02 \x01(\tR\vrelatedMemo\":\n" + "\frelated_memo\x18\x02 \x01(\tR\vrelatedMemo\x12!\n" +
"\fmemo_snippet\x18\x03 \x01(\tR\vmemoSnippet\x120\n" +
"\x14related_memo_snippet\x18\x04 \x01(\tR\x12relatedMemoSnippet\x1a\xa0\x01\n" +
"\x12MemoMentionPayload\x12\x12\n" +
"\x04memo\x18\x01 \x01(\tR\x04memo\x12!\n" +
"\frelated_memo\x18\x02 \x01(\tR\vrelatedMemo\x12!\n" +
"\fmemo_snippet\x18\x03 \x01(\tR\vmemoSnippet\x120\n" +
"\x14related_memo_snippet\x18\x04 \x01(\tR\x12relatedMemoSnippet\":\n" +
"\x06Status\x12\x16\n" + "\x06Status\x12\x16\n" +
"\x12STATUS_UNSPECIFIED\x10\x00\x12\n" + "\x12STATUS_UNSPECIFIED\x10\x00\x12\n" +
"\n" + "\n" +
"\x06UNREAD\x10\x01\x12\f\n" + "\x06UNREAD\x10\x01\x12\f\n" +
"\bARCHIVED\x10\x02\".\n" + "\bARCHIVED\x10\x02\"@\n" +
"\x04Type\x12\x14\n" + "\x04Type\x12\x14\n" +
"\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" + "\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" +
"\fMEMO_COMMENT\x10\x01:p\xeaAm\n" + "\fMEMO_COMMENT\x10\x01\x12\x10\n" +
"\fMEMO_MENTION\x10\x02:p\xeaAm\n" +
"\x1dmemos.api.v1/UserNotification\x12)users/{user}/notifications/{notification}\x1a\x04name*\rnotifications2\fnotificationB\t\n" + "\x1dmemos.api.v1/UserNotification\x12)users/{user}/notifications/{notification}\x1a\x04name*\rnotifications2\fnotificationB\t\n" +
"\apayload\"\xb4\x01\n" + "\apayload\"\xb4\x01\n" +
"\x1cListUserNotificationsRequest\x121\n" + "\x1cListUserNotificationsRequest\x121\n" +
...@@ -2780,9 +3003,10 @@ const file_api_v1_user_service_proto_rawDesc = "" + ...@@ -2780,9 +3003,10 @@ const file_api_v1_user_service_proto_rawDesc = "" +
"updateMask\"Z\n" + "updateMask\"Z\n" +
"\x1dDeleteUserNotificationRequest\x129\n" + "\x1dDeleteUserNotificationRequest\x129\n" +
"\x04name\x18\x01 \x01(\tB%\xe0A\x02\xfaA\x1f\n" + "\x04name\x18\x01 \x01(\tB%\xe0A\x02\xfaA\x1f\n" +
"\x1dmemos.api.v1/UserNotificationR\x04name2\x83\x17\n" + "\x1dmemos.api.v1/UserNotificationR\x04name2\x80\x18\n" +
"\vUserService\x12c\n" + "\vUserService\x12c\n" +
"\tListUsers\x12\x1e.memos.api.v1.ListUsersRequest\x1a\x1f.memos.api.v1.ListUsersResponse\"\x15\x82\xd3\xe4\x93\x02\x0f\x12\r/api/v1/users\x12b\n" + "\tListUsers\x12\x1e.memos.api.v1.ListUsersRequest\x1a\x1f.memos.api.v1.ListUsersResponse\"\x15\x82\xd3\xe4\x93\x02\x0f\x12\r/api/v1/users\x12{\n" +
"\rBatchGetUsers\x12\".memos.api.v1.BatchGetUsersRequest\x1a#.memos.api.v1.BatchGetUsersResponse\"!\x82\xd3\xe4\x93\x02\x1b:\x01*\"\x16/api/v1/users:batchGet\x12b\n" +
"\aGetUser\x12\x1c.memos.api.v1.GetUserRequest\x1a\x12.memos.api.v1.User\"%\xdaA\x04name\x82\xd3\xe4\x93\x02\x18\x12\x16/api/v1/{name=users/*}\x12e\n" + "\aGetUser\x12\x1c.memos.api.v1.GetUserRequest\x1a\x12.memos.api.v1.User\"%\xdaA\x04name\x82\xd3\xe4\x93\x02\x18\x12\x16/api/v1/{name=users/*}\x12e\n" +
"\n" + "\n" +
"CreateUser\x12\x1f.memos.api.v1.CreateUserRequest\x1a\x12.memos.api.v1.User\"\"\xdaA\x04user\x82\xd3\xe4\x93\x02\x15:\x04user\"\r/api/v1/users\x12\x7f\n" + "CreateUser\x12\x1f.memos.api.v1.CreateUserRequest\x1a\x12.memos.api.v1.User\"\"\xdaA\x04user\x82\xd3\xe4\x93\x02\x15:\x04user\"\r/api/v1/users\x12\x7f\n" +
...@@ -2820,7 +3044,7 @@ func file_api_v1_user_service_proto_rawDescGZIP() []byte { ...@@ -2820,7 +3044,7 @@ func file_api_v1_user_service_proto_rawDescGZIP() []byte {
} }
var file_api_v1_user_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) var file_api_v1_user_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
var file_api_v1_user_service_proto_msgTypes = make([]protoimpl.MessageInfo, 38) var file_api_v1_user_service_proto_msgTypes = make([]protoimpl.MessageInfo, 41)
var file_api_v1_user_service_proto_goTypes = []any{ var file_api_v1_user_service_proto_goTypes = []any{
(User_Role)(0), // 0: memos.api.v1.User.Role (User_Role)(0), // 0: memos.api.v1.User.Role
(UserSetting_Key)(0), // 1: memos.api.v1.UserSetting.Key (UserSetting_Key)(0), // 1: memos.api.v1.UserSetting.Key
...@@ -2829,129 +3053,137 @@ var file_api_v1_user_service_proto_goTypes = []any{ ...@@ -2829,129 +3053,137 @@ var file_api_v1_user_service_proto_goTypes = []any{
(*User)(nil), // 4: memos.api.v1.User (*User)(nil), // 4: memos.api.v1.User
(*ListUsersRequest)(nil), // 5: memos.api.v1.ListUsersRequest (*ListUsersRequest)(nil), // 5: memos.api.v1.ListUsersRequest
(*ListUsersResponse)(nil), // 6: memos.api.v1.ListUsersResponse (*ListUsersResponse)(nil), // 6: memos.api.v1.ListUsersResponse
(*GetUserRequest)(nil), // 7: memos.api.v1.GetUserRequest (*BatchGetUsersRequest)(nil), // 7: memos.api.v1.BatchGetUsersRequest
(*CreateUserRequest)(nil), // 8: memos.api.v1.CreateUserRequest (*BatchGetUsersResponse)(nil), // 8: memos.api.v1.BatchGetUsersResponse
(*UpdateUserRequest)(nil), // 9: memos.api.v1.UpdateUserRequest (*GetUserRequest)(nil), // 9: memos.api.v1.GetUserRequest
(*DeleteUserRequest)(nil), // 10: memos.api.v1.DeleteUserRequest (*CreateUserRequest)(nil), // 10: memos.api.v1.CreateUserRequest
(*UserStats)(nil), // 11: memos.api.v1.UserStats (*UpdateUserRequest)(nil), // 11: memos.api.v1.UpdateUserRequest
(*GetUserStatsRequest)(nil), // 12: memos.api.v1.GetUserStatsRequest (*DeleteUserRequest)(nil), // 12: memos.api.v1.DeleteUserRequest
(*ListAllUserStatsRequest)(nil), // 13: memos.api.v1.ListAllUserStatsRequest (*UserStats)(nil), // 13: memos.api.v1.UserStats
(*ListAllUserStatsResponse)(nil), // 14: memos.api.v1.ListAllUserStatsResponse (*GetUserStatsRequest)(nil), // 14: memos.api.v1.GetUserStatsRequest
(*UserSetting)(nil), // 15: memos.api.v1.UserSetting (*ListAllUserStatsRequest)(nil), // 15: memos.api.v1.ListAllUserStatsRequest
(*GetUserSettingRequest)(nil), // 16: memos.api.v1.GetUserSettingRequest (*ListAllUserStatsResponse)(nil), // 16: memos.api.v1.ListAllUserStatsResponse
(*UpdateUserSettingRequest)(nil), // 17: memos.api.v1.UpdateUserSettingRequest (*UserSetting)(nil), // 17: memos.api.v1.UserSetting
(*ListUserSettingsRequest)(nil), // 18: memos.api.v1.ListUserSettingsRequest (*GetUserSettingRequest)(nil), // 18: memos.api.v1.GetUserSettingRequest
(*ListUserSettingsResponse)(nil), // 19: memos.api.v1.ListUserSettingsResponse (*UpdateUserSettingRequest)(nil), // 19: memos.api.v1.UpdateUserSettingRequest
(*PersonalAccessToken)(nil), // 20: memos.api.v1.PersonalAccessToken (*ListUserSettingsRequest)(nil), // 20: memos.api.v1.ListUserSettingsRequest
(*ListPersonalAccessTokensRequest)(nil), // 21: memos.api.v1.ListPersonalAccessTokensRequest (*ListUserSettingsResponse)(nil), // 21: memos.api.v1.ListUserSettingsResponse
(*ListPersonalAccessTokensResponse)(nil), // 22: memos.api.v1.ListPersonalAccessTokensResponse (*PersonalAccessToken)(nil), // 22: memos.api.v1.PersonalAccessToken
(*CreatePersonalAccessTokenRequest)(nil), // 23: memos.api.v1.CreatePersonalAccessTokenRequest (*ListPersonalAccessTokensRequest)(nil), // 23: memos.api.v1.ListPersonalAccessTokensRequest
(*CreatePersonalAccessTokenResponse)(nil), // 24: memos.api.v1.CreatePersonalAccessTokenResponse (*ListPersonalAccessTokensResponse)(nil), // 24: memos.api.v1.ListPersonalAccessTokensResponse
(*DeletePersonalAccessTokenRequest)(nil), // 25: memos.api.v1.DeletePersonalAccessTokenRequest (*CreatePersonalAccessTokenRequest)(nil), // 25: memos.api.v1.CreatePersonalAccessTokenRequest
(*UserWebhook)(nil), // 26: memos.api.v1.UserWebhook (*CreatePersonalAccessTokenResponse)(nil), // 26: memos.api.v1.CreatePersonalAccessTokenResponse
(*ListUserWebhooksRequest)(nil), // 27: memos.api.v1.ListUserWebhooksRequest (*DeletePersonalAccessTokenRequest)(nil), // 27: memos.api.v1.DeletePersonalAccessTokenRequest
(*ListUserWebhooksResponse)(nil), // 28: memos.api.v1.ListUserWebhooksResponse (*UserWebhook)(nil), // 28: memos.api.v1.UserWebhook
(*CreateUserWebhookRequest)(nil), // 29: memos.api.v1.CreateUserWebhookRequest (*ListUserWebhooksRequest)(nil), // 29: memos.api.v1.ListUserWebhooksRequest
(*UpdateUserWebhookRequest)(nil), // 30: memos.api.v1.UpdateUserWebhookRequest (*ListUserWebhooksResponse)(nil), // 30: memos.api.v1.ListUserWebhooksResponse
(*DeleteUserWebhookRequest)(nil), // 31: memos.api.v1.DeleteUserWebhookRequest (*CreateUserWebhookRequest)(nil), // 31: memos.api.v1.CreateUserWebhookRequest
(*UserNotification)(nil), // 32: memos.api.v1.UserNotification (*UpdateUserWebhookRequest)(nil), // 32: memos.api.v1.UpdateUserWebhookRequest
(*ListUserNotificationsRequest)(nil), // 33: memos.api.v1.ListUserNotificationsRequest (*DeleteUserWebhookRequest)(nil), // 33: memos.api.v1.DeleteUserWebhookRequest
(*ListUserNotificationsResponse)(nil), // 34: memos.api.v1.ListUserNotificationsResponse (*UserNotification)(nil), // 34: memos.api.v1.UserNotification
(*UpdateUserNotificationRequest)(nil), // 35: memos.api.v1.UpdateUserNotificationRequest (*ListUserNotificationsRequest)(nil), // 35: memos.api.v1.ListUserNotificationsRequest
(*DeleteUserNotificationRequest)(nil), // 36: memos.api.v1.DeleteUserNotificationRequest (*ListUserNotificationsResponse)(nil), // 36: memos.api.v1.ListUserNotificationsResponse
nil, // 37: memos.api.v1.UserStats.TagCountEntry (*UpdateUserNotificationRequest)(nil), // 37: memos.api.v1.UpdateUserNotificationRequest
(*UserStats_MemoTypeStats)(nil), // 38: memos.api.v1.UserStats.MemoTypeStats (*DeleteUserNotificationRequest)(nil), // 38: memos.api.v1.DeleteUserNotificationRequest
(*UserSetting_GeneralSetting)(nil), // 39: memos.api.v1.UserSetting.GeneralSetting nil, // 39: memos.api.v1.UserStats.TagCountEntry
(*UserSetting_WebhooksSetting)(nil), // 40: memos.api.v1.UserSetting.WebhooksSetting (*UserStats_MemoTypeStats)(nil), // 40: memos.api.v1.UserStats.MemoTypeStats
(*UserNotification_MemoCommentPayload)(nil), // 41: memos.api.v1.UserNotification.MemoCommentPayload (*UserSetting_GeneralSetting)(nil), // 41: memos.api.v1.UserSetting.GeneralSetting
(State)(0), // 42: memos.api.v1.State (*UserSetting_WebhooksSetting)(nil), // 42: memos.api.v1.UserSetting.WebhooksSetting
(*timestamppb.Timestamp)(nil), // 43: google.protobuf.Timestamp (*UserNotification_MemoCommentPayload)(nil), // 43: memos.api.v1.UserNotification.MemoCommentPayload
(*fieldmaskpb.FieldMask)(nil), // 44: google.protobuf.FieldMask (*UserNotification_MemoMentionPayload)(nil), // 44: memos.api.v1.UserNotification.MemoMentionPayload
(*emptypb.Empty)(nil), // 45: google.protobuf.Empty (State)(0), // 45: memos.api.v1.State
(*timestamppb.Timestamp)(nil), // 46: google.protobuf.Timestamp
(*fieldmaskpb.FieldMask)(nil), // 47: google.protobuf.FieldMask
(*emptypb.Empty)(nil), // 48: google.protobuf.Empty
} }
var file_api_v1_user_service_proto_depIdxs = []int32{ var file_api_v1_user_service_proto_depIdxs = []int32{
0, // 0: memos.api.v1.User.role:type_name -> memos.api.v1.User.Role 0, // 0: memos.api.v1.User.role:type_name -> memos.api.v1.User.Role
42, // 1: memos.api.v1.User.state:type_name -> memos.api.v1.State 45, // 1: memos.api.v1.User.state:type_name -> memos.api.v1.State
43, // 2: memos.api.v1.User.create_time:type_name -> google.protobuf.Timestamp 46, // 2: memos.api.v1.User.create_time:type_name -> google.protobuf.Timestamp
43, // 3: memos.api.v1.User.update_time:type_name -> google.protobuf.Timestamp 46, // 3: memos.api.v1.User.update_time:type_name -> google.protobuf.Timestamp
4, // 4: memos.api.v1.ListUsersResponse.users:type_name -> memos.api.v1.User 4, // 4: memos.api.v1.ListUsersResponse.users:type_name -> memos.api.v1.User
44, // 5: memos.api.v1.GetUserRequest.read_mask:type_name -> google.protobuf.FieldMask 4, // 5: memos.api.v1.BatchGetUsersResponse.users:type_name -> memos.api.v1.User
4, // 6: memos.api.v1.CreateUserRequest.user:type_name -> memos.api.v1.User 47, // 6: memos.api.v1.GetUserRequest.read_mask:type_name -> google.protobuf.FieldMask
4, // 7: memos.api.v1.UpdateUserRequest.user:type_name -> memos.api.v1.User 4, // 7: memos.api.v1.CreateUserRequest.user:type_name -> memos.api.v1.User
44, // 8: memos.api.v1.UpdateUserRequest.update_mask:type_name -> google.protobuf.FieldMask 4, // 8: memos.api.v1.UpdateUserRequest.user:type_name -> memos.api.v1.User
43, // 9: memos.api.v1.UserStats.memo_display_timestamps:type_name -> google.protobuf.Timestamp 47, // 9: memos.api.v1.UpdateUserRequest.update_mask:type_name -> google.protobuf.FieldMask
38, // 10: memos.api.v1.UserStats.memo_type_stats:type_name -> memos.api.v1.UserStats.MemoTypeStats 46, // 10: memos.api.v1.UserStats.memo_display_timestamps:type_name -> google.protobuf.Timestamp
37, // 11: memos.api.v1.UserStats.tag_count:type_name -> memos.api.v1.UserStats.TagCountEntry 40, // 11: memos.api.v1.UserStats.memo_type_stats:type_name -> memos.api.v1.UserStats.MemoTypeStats
11, // 12: memos.api.v1.ListAllUserStatsResponse.stats:type_name -> memos.api.v1.UserStats 39, // 12: memos.api.v1.UserStats.tag_count:type_name -> memos.api.v1.UserStats.TagCountEntry
39, // 13: memos.api.v1.UserSetting.general_setting:type_name -> memos.api.v1.UserSetting.GeneralSetting 13, // 13: memos.api.v1.ListAllUserStatsResponse.stats:type_name -> memos.api.v1.UserStats
40, // 14: memos.api.v1.UserSetting.webhooks_setting:type_name -> memos.api.v1.UserSetting.WebhooksSetting 41, // 14: memos.api.v1.UserSetting.general_setting:type_name -> memos.api.v1.UserSetting.GeneralSetting
15, // 15: memos.api.v1.UpdateUserSettingRequest.setting:type_name -> memos.api.v1.UserSetting 42, // 15: memos.api.v1.UserSetting.webhooks_setting:type_name -> memos.api.v1.UserSetting.WebhooksSetting
44, // 16: memos.api.v1.UpdateUserSettingRequest.update_mask:type_name -> google.protobuf.FieldMask 17, // 16: memos.api.v1.UpdateUserSettingRequest.setting:type_name -> memos.api.v1.UserSetting
15, // 17: memos.api.v1.ListUserSettingsResponse.settings:type_name -> memos.api.v1.UserSetting 47, // 17: memos.api.v1.UpdateUserSettingRequest.update_mask:type_name -> google.protobuf.FieldMask
43, // 18: memos.api.v1.PersonalAccessToken.created_at:type_name -> google.protobuf.Timestamp 17, // 18: memos.api.v1.ListUserSettingsResponse.settings:type_name -> memos.api.v1.UserSetting
43, // 19: memos.api.v1.PersonalAccessToken.expires_at:type_name -> google.protobuf.Timestamp 46, // 19: memos.api.v1.PersonalAccessToken.created_at:type_name -> google.protobuf.Timestamp
43, // 20: memos.api.v1.PersonalAccessToken.last_used_at:type_name -> google.protobuf.Timestamp 46, // 20: memos.api.v1.PersonalAccessToken.expires_at:type_name -> google.protobuf.Timestamp
20, // 21: memos.api.v1.ListPersonalAccessTokensResponse.personal_access_tokens:type_name -> memos.api.v1.PersonalAccessToken 46, // 21: memos.api.v1.PersonalAccessToken.last_used_at:type_name -> google.protobuf.Timestamp
20, // 22: memos.api.v1.CreatePersonalAccessTokenResponse.personal_access_token:type_name -> memos.api.v1.PersonalAccessToken 22, // 22: memos.api.v1.ListPersonalAccessTokensResponse.personal_access_tokens:type_name -> memos.api.v1.PersonalAccessToken
43, // 23: memos.api.v1.UserWebhook.create_time:type_name -> google.protobuf.Timestamp 22, // 23: memos.api.v1.CreatePersonalAccessTokenResponse.personal_access_token:type_name -> memos.api.v1.PersonalAccessToken
43, // 24: memos.api.v1.UserWebhook.update_time:type_name -> google.protobuf.Timestamp 46, // 24: memos.api.v1.UserWebhook.create_time:type_name -> google.protobuf.Timestamp
26, // 25: memos.api.v1.ListUserWebhooksResponse.webhooks:type_name -> memos.api.v1.UserWebhook 46, // 25: memos.api.v1.UserWebhook.update_time:type_name -> google.protobuf.Timestamp
26, // 26: memos.api.v1.CreateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook 28, // 26: memos.api.v1.ListUserWebhooksResponse.webhooks:type_name -> memos.api.v1.UserWebhook
26, // 27: memos.api.v1.UpdateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook 28, // 27: memos.api.v1.CreateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook
44, // 28: memos.api.v1.UpdateUserWebhookRequest.update_mask:type_name -> google.protobuf.FieldMask 28, // 28: memos.api.v1.UpdateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook
2, // 29: memos.api.v1.UserNotification.status:type_name -> memos.api.v1.UserNotification.Status 47, // 29: memos.api.v1.UpdateUserWebhookRequest.update_mask:type_name -> google.protobuf.FieldMask
43, // 30: memos.api.v1.UserNotification.create_time:type_name -> google.protobuf.Timestamp 4, // 30: memos.api.v1.UserNotification.sender_user:type_name -> memos.api.v1.User
3, // 31: memos.api.v1.UserNotification.type:type_name -> memos.api.v1.UserNotification.Type 2, // 31: memos.api.v1.UserNotification.status:type_name -> memos.api.v1.UserNotification.Status
41, // 32: memos.api.v1.UserNotification.memo_comment:type_name -> memos.api.v1.UserNotification.MemoCommentPayload 46, // 32: memos.api.v1.UserNotification.create_time:type_name -> google.protobuf.Timestamp
32, // 33: memos.api.v1.ListUserNotificationsResponse.notifications:type_name -> memos.api.v1.UserNotification 3, // 33: memos.api.v1.UserNotification.type:type_name -> memos.api.v1.UserNotification.Type
32, // 34: memos.api.v1.UpdateUserNotificationRequest.notification:type_name -> memos.api.v1.UserNotification 43, // 34: memos.api.v1.UserNotification.memo_comment:type_name -> memos.api.v1.UserNotification.MemoCommentPayload
44, // 35: memos.api.v1.UpdateUserNotificationRequest.update_mask:type_name -> google.protobuf.FieldMask 44, // 35: memos.api.v1.UserNotification.memo_mention:type_name -> memos.api.v1.UserNotification.MemoMentionPayload
26, // 36: memos.api.v1.UserSetting.WebhooksSetting.webhooks:type_name -> memos.api.v1.UserWebhook 34, // 36: memos.api.v1.ListUserNotificationsResponse.notifications:type_name -> memos.api.v1.UserNotification
5, // 37: memos.api.v1.UserService.ListUsers:input_type -> memos.api.v1.ListUsersRequest 34, // 37: memos.api.v1.UpdateUserNotificationRequest.notification:type_name -> memos.api.v1.UserNotification
7, // 38: memos.api.v1.UserService.GetUser:input_type -> memos.api.v1.GetUserRequest 47, // 38: memos.api.v1.UpdateUserNotificationRequest.update_mask:type_name -> google.protobuf.FieldMask
8, // 39: memos.api.v1.UserService.CreateUser:input_type -> memos.api.v1.CreateUserRequest 28, // 39: memos.api.v1.UserSetting.WebhooksSetting.webhooks:type_name -> memos.api.v1.UserWebhook
9, // 40: memos.api.v1.UserService.UpdateUser:input_type -> memos.api.v1.UpdateUserRequest 5, // 40: memos.api.v1.UserService.ListUsers:input_type -> memos.api.v1.ListUsersRequest
10, // 41: memos.api.v1.UserService.DeleteUser:input_type -> memos.api.v1.DeleteUserRequest 7, // 41: memos.api.v1.UserService.BatchGetUsers:input_type -> memos.api.v1.BatchGetUsersRequest
13, // 42: memos.api.v1.UserService.ListAllUserStats:input_type -> memos.api.v1.ListAllUserStatsRequest 9, // 42: memos.api.v1.UserService.GetUser:input_type -> memos.api.v1.GetUserRequest
12, // 43: memos.api.v1.UserService.GetUserStats:input_type -> memos.api.v1.GetUserStatsRequest 10, // 43: memos.api.v1.UserService.CreateUser:input_type -> memos.api.v1.CreateUserRequest
16, // 44: memos.api.v1.UserService.GetUserSetting:input_type -> memos.api.v1.GetUserSettingRequest 11, // 44: memos.api.v1.UserService.UpdateUser:input_type -> memos.api.v1.UpdateUserRequest
17, // 45: memos.api.v1.UserService.UpdateUserSetting:input_type -> memos.api.v1.UpdateUserSettingRequest 12, // 45: memos.api.v1.UserService.DeleteUser:input_type -> memos.api.v1.DeleteUserRequest
18, // 46: memos.api.v1.UserService.ListUserSettings:input_type -> memos.api.v1.ListUserSettingsRequest 15, // 46: memos.api.v1.UserService.ListAllUserStats:input_type -> memos.api.v1.ListAllUserStatsRequest
21, // 47: memos.api.v1.UserService.ListPersonalAccessTokens:input_type -> memos.api.v1.ListPersonalAccessTokensRequest 14, // 47: memos.api.v1.UserService.GetUserStats:input_type -> memos.api.v1.GetUserStatsRequest
23, // 48: memos.api.v1.UserService.CreatePersonalAccessToken:input_type -> memos.api.v1.CreatePersonalAccessTokenRequest 18, // 48: memos.api.v1.UserService.GetUserSetting:input_type -> memos.api.v1.GetUserSettingRequest
25, // 49: memos.api.v1.UserService.DeletePersonalAccessToken:input_type -> memos.api.v1.DeletePersonalAccessTokenRequest 19, // 49: memos.api.v1.UserService.UpdateUserSetting:input_type -> memos.api.v1.UpdateUserSettingRequest
27, // 50: memos.api.v1.UserService.ListUserWebhooks:input_type -> memos.api.v1.ListUserWebhooksRequest 20, // 50: memos.api.v1.UserService.ListUserSettings:input_type -> memos.api.v1.ListUserSettingsRequest
29, // 51: memos.api.v1.UserService.CreateUserWebhook:input_type -> memos.api.v1.CreateUserWebhookRequest 23, // 51: memos.api.v1.UserService.ListPersonalAccessTokens:input_type -> memos.api.v1.ListPersonalAccessTokensRequest
30, // 52: memos.api.v1.UserService.UpdateUserWebhook:input_type -> memos.api.v1.UpdateUserWebhookRequest 25, // 52: memos.api.v1.UserService.CreatePersonalAccessToken:input_type -> memos.api.v1.CreatePersonalAccessTokenRequest
31, // 53: memos.api.v1.UserService.DeleteUserWebhook:input_type -> memos.api.v1.DeleteUserWebhookRequest 27, // 53: memos.api.v1.UserService.DeletePersonalAccessToken:input_type -> memos.api.v1.DeletePersonalAccessTokenRequest
33, // 54: memos.api.v1.UserService.ListUserNotifications:input_type -> memos.api.v1.ListUserNotificationsRequest 29, // 54: memos.api.v1.UserService.ListUserWebhooks:input_type -> memos.api.v1.ListUserWebhooksRequest
35, // 55: memos.api.v1.UserService.UpdateUserNotification:input_type -> memos.api.v1.UpdateUserNotificationRequest 31, // 55: memos.api.v1.UserService.CreateUserWebhook:input_type -> memos.api.v1.CreateUserWebhookRequest
36, // 56: memos.api.v1.UserService.DeleteUserNotification:input_type -> memos.api.v1.DeleteUserNotificationRequest 32, // 56: memos.api.v1.UserService.UpdateUserWebhook:input_type -> memos.api.v1.UpdateUserWebhookRequest
6, // 57: memos.api.v1.UserService.ListUsers:output_type -> memos.api.v1.ListUsersResponse 33, // 57: memos.api.v1.UserService.DeleteUserWebhook:input_type -> memos.api.v1.DeleteUserWebhookRequest
4, // 58: memos.api.v1.UserService.GetUser:output_type -> memos.api.v1.User 35, // 58: memos.api.v1.UserService.ListUserNotifications:input_type -> memos.api.v1.ListUserNotificationsRequest
4, // 59: memos.api.v1.UserService.CreateUser:output_type -> memos.api.v1.User 37, // 59: memos.api.v1.UserService.UpdateUserNotification:input_type -> memos.api.v1.UpdateUserNotificationRequest
4, // 60: memos.api.v1.UserService.UpdateUser:output_type -> memos.api.v1.User 38, // 60: memos.api.v1.UserService.DeleteUserNotification:input_type -> memos.api.v1.DeleteUserNotificationRequest
45, // 61: memos.api.v1.UserService.DeleteUser:output_type -> google.protobuf.Empty 6, // 61: memos.api.v1.UserService.ListUsers:output_type -> memos.api.v1.ListUsersResponse
14, // 62: memos.api.v1.UserService.ListAllUserStats:output_type -> memos.api.v1.ListAllUserStatsResponse 8, // 62: memos.api.v1.UserService.BatchGetUsers:output_type -> memos.api.v1.BatchGetUsersResponse
11, // 63: memos.api.v1.UserService.GetUserStats:output_type -> memos.api.v1.UserStats 4, // 63: memos.api.v1.UserService.GetUser:output_type -> memos.api.v1.User
15, // 64: memos.api.v1.UserService.GetUserSetting:output_type -> memos.api.v1.UserSetting 4, // 64: memos.api.v1.UserService.CreateUser:output_type -> memos.api.v1.User
15, // 65: memos.api.v1.UserService.UpdateUserSetting:output_type -> memos.api.v1.UserSetting 4, // 65: memos.api.v1.UserService.UpdateUser:output_type -> memos.api.v1.User
19, // 66: memos.api.v1.UserService.ListUserSettings:output_type -> memos.api.v1.ListUserSettingsResponse 48, // 66: memos.api.v1.UserService.DeleteUser:output_type -> google.protobuf.Empty
22, // 67: memos.api.v1.UserService.ListPersonalAccessTokens:output_type -> memos.api.v1.ListPersonalAccessTokensResponse 16, // 67: memos.api.v1.UserService.ListAllUserStats:output_type -> memos.api.v1.ListAllUserStatsResponse
24, // 68: memos.api.v1.UserService.CreatePersonalAccessToken:output_type -> memos.api.v1.CreatePersonalAccessTokenResponse 13, // 68: memos.api.v1.UserService.GetUserStats:output_type -> memos.api.v1.UserStats
45, // 69: memos.api.v1.UserService.DeletePersonalAccessToken:output_type -> google.protobuf.Empty 17, // 69: memos.api.v1.UserService.GetUserSetting:output_type -> memos.api.v1.UserSetting
28, // 70: memos.api.v1.UserService.ListUserWebhooks:output_type -> memos.api.v1.ListUserWebhooksResponse 17, // 70: memos.api.v1.UserService.UpdateUserSetting:output_type -> memos.api.v1.UserSetting
26, // 71: memos.api.v1.UserService.CreateUserWebhook:output_type -> memos.api.v1.UserWebhook 21, // 71: memos.api.v1.UserService.ListUserSettings:output_type -> memos.api.v1.ListUserSettingsResponse
26, // 72: memos.api.v1.UserService.UpdateUserWebhook:output_type -> memos.api.v1.UserWebhook 24, // 72: memos.api.v1.UserService.ListPersonalAccessTokens:output_type -> memos.api.v1.ListPersonalAccessTokensResponse
45, // 73: memos.api.v1.UserService.DeleteUserWebhook:output_type -> google.protobuf.Empty 26, // 73: memos.api.v1.UserService.CreatePersonalAccessToken:output_type -> memos.api.v1.CreatePersonalAccessTokenResponse
34, // 74: memos.api.v1.UserService.ListUserNotifications:output_type -> memos.api.v1.ListUserNotificationsResponse 48, // 74: memos.api.v1.UserService.DeletePersonalAccessToken:output_type -> google.protobuf.Empty
32, // 75: memos.api.v1.UserService.UpdateUserNotification:output_type -> memos.api.v1.UserNotification 30, // 75: memos.api.v1.UserService.ListUserWebhooks:output_type -> memos.api.v1.ListUserWebhooksResponse
45, // 76: memos.api.v1.UserService.DeleteUserNotification:output_type -> google.protobuf.Empty 28, // 76: memos.api.v1.UserService.CreateUserWebhook:output_type -> memos.api.v1.UserWebhook
57, // [57:77] is the sub-list for method output_type 28, // 77: memos.api.v1.UserService.UpdateUserWebhook:output_type -> memos.api.v1.UserWebhook
37, // [37:57] is the sub-list for method input_type 48, // 78: memos.api.v1.UserService.DeleteUserWebhook:output_type -> google.protobuf.Empty
37, // [37:37] is the sub-list for extension type_name 36, // 79: memos.api.v1.UserService.ListUserNotifications:output_type -> memos.api.v1.ListUserNotificationsResponse
37, // [37:37] is the sub-list for extension extendee 34, // 80: memos.api.v1.UserService.UpdateUserNotification:output_type -> memos.api.v1.UserNotification
0, // [0:37] is the sub-list for field type_name 48, // 81: memos.api.v1.UserService.DeleteUserNotification:output_type -> google.protobuf.Empty
61, // [61:82] is the sub-list for method output_type
40, // [40:61] is the sub-list for method input_type
40, // [40:40] is the sub-list for extension type_name
40, // [40:40] is the sub-list for extension extendee
0, // [0:40] is the sub-list for field type_name
} }
func init() { file_api_v1_user_service_proto_init() } func init() { file_api_v1_user_service_proto_init() }
...@@ -2960,12 +3192,13 @@ func file_api_v1_user_service_proto_init() { ...@@ -2960,12 +3192,13 @@ func file_api_v1_user_service_proto_init() {
return return
} }
file_api_v1_common_proto_init() file_api_v1_common_proto_init()
file_api_v1_user_service_proto_msgTypes[11].OneofWrappers = []any{ file_api_v1_user_service_proto_msgTypes[13].OneofWrappers = []any{
(*UserSetting_GeneralSetting_)(nil), (*UserSetting_GeneralSetting_)(nil),
(*UserSetting_WebhooksSetting_)(nil), (*UserSetting_WebhooksSetting_)(nil),
} }
file_api_v1_user_service_proto_msgTypes[28].OneofWrappers = []any{ file_api_v1_user_service_proto_msgTypes[30].OneofWrappers = []any{
(*UserNotification_MemoComment)(nil), (*UserNotification_MemoComment)(nil),
(*UserNotification_MemoMention)(nil),
} }
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
...@@ -2973,7 +3206,7 @@ func file_api_v1_user_service_proto_init() { ...@@ -2973,7 +3206,7 @@ func file_api_v1_user_service_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v1_user_service_proto_rawDesc), len(file_api_v1_user_service_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v1_user_service_proto_rawDesc), len(file_api_v1_user_service_proto_rawDesc)),
NumEnums: 4, NumEnums: 4,
NumMessages: 38, NumMessages: 41,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
......
...@@ -70,6 +70,33 @@ func local_request_UserService_ListUsers_0(ctx context.Context, marshaler runtim ...@@ -70,6 +70,33 @@ func local_request_UserService_ListUsers_0(ctx context.Context, marshaler runtim
return msg, metadata, err return msg, metadata, err
} }
func request_UserService_BatchGetUsers_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq BatchGetUsersRequest
metadata runtime.ServerMetadata
)
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if req.Body != nil {
_, _ = io.Copy(io.Discard, req.Body)
}
msg, err := client.BatchGetUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_BatchGetUsers_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq BatchGetUsersRequest
metadata runtime.ServerMetadata
)
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.BatchGetUsers(ctx, &protoReq)
return msg, metadata, err
}
var filter_UserService_GetUser_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} var filter_UserService_GetUser_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
func request_UserService_GetUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_UserService_GetUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
...@@ -1071,6 +1098,26 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux ...@@ -1071,6 +1098,26 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
} }
forward_UserService_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) forward_UserService_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
}) })
mux.Handle(http.MethodPost, pattern_UserService_BatchGetUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.UserService/BatchGetUsers", runtime.WithHTTPPathPattern("/api/v1/users:batchGet"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_BatchGetUsers_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_BatchGetUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodGet, pattern_UserService_GetUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle(http.MethodGet, pattern_UserService_GetUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
...@@ -1508,6 +1555,23 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux ...@@ -1508,6 +1555,23 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
} }
forward_UserService_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) forward_UserService_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
}) })
mux.Handle(http.MethodPost, pattern_UserService_BatchGetUsers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/BatchGetUsers", runtime.WithHTTPPathPattern("/api/v1/users:batchGet"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_BatchGetUsers_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_BatchGetUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodGet, pattern_UserService_GetUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle(http.MethodGet, pattern_UserService_GetUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
...@@ -1836,6 +1900,7 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux ...@@ -1836,6 +1900,7 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
var ( var (
pattern_UserService_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "")) pattern_UserService_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, ""))
pattern_UserService_BatchGetUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "batchGet"))
pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "")) pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, ""))
pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "")) pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, ""))
pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, "")) pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, ""))
...@@ -1859,6 +1924,7 @@ var ( ...@@ -1859,6 +1924,7 @@ var (
var ( var (
forward_UserService_ListUsers_0 = runtime.ForwardResponseMessage forward_UserService_ListUsers_0 = runtime.ForwardResponseMessage
forward_UserService_BatchGetUsers_0 = runtime.ForwardResponseMessage
forward_UserService_GetUser_0 = runtime.ForwardResponseMessage forward_UserService_GetUser_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage
......
...@@ -21,6 +21,7 @@ const _ = grpc.SupportPackageIsVersion9 ...@@ -21,6 +21,7 @@ const _ = grpc.SupportPackageIsVersion9
const ( const (
UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers" UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers"
UserService_BatchGetUsers_FullMethodName = "/memos.api.v1.UserService/BatchGetUsers"
UserService_GetUser_FullMethodName = "/memos.api.v1.UserService/GetUser" UserService_GetUser_FullMethodName = "/memos.api.v1.UserService/GetUser"
UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser" UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser" UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser"
...@@ -48,6 +49,8 @@ const ( ...@@ -48,6 +49,8 @@ const (
type UserServiceClient interface { type UserServiceClient interface {
// ListUsers returns a list of users. // ListUsers returns a list of users.
ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error)
// BatchGetUsers returns active users by usernames.
BatchGetUsers(ctx context.Context, in *BatchGetUsersRequest, opts ...grpc.CallOption) (*BatchGetUsersResponse, error)
// GetUser gets a user by username. // GetUser gets a user by username.
// Format: users/{username} (e.g., users/steven) // Format: users/{username} (e.g., users/steven)
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
...@@ -109,6 +112,16 @@ func (c *userServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest, ...@@ -109,6 +112,16 @@ func (c *userServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest,
return out, nil return out, nil
} }
func (c *userServiceClient) BatchGetUsers(ctx context.Context, in *BatchGetUsersRequest, opts ...grpc.CallOption) (*BatchGetUsersResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(BatchGetUsersResponse)
err := c.cc.Invoke(ctx, UserService_BatchGetUsers_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) { func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(User) out := new(User)
...@@ -305,6 +318,8 @@ func (c *userServiceClient) DeleteUserNotification(ctx context.Context, in *Dele ...@@ -305,6 +318,8 @@ func (c *userServiceClient) DeleteUserNotification(ctx context.Context, in *Dele
type UserServiceServer interface { type UserServiceServer interface {
// ListUsers returns a list of users. // ListUsers returns a list of users.
ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error)
// BatchGetUsers returns active users by usernames.
BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error)
// GetUser gets a user by username. // GetUser gets a user by username.
// Format: users/{username} (e.g., users/steven) // Format: users/{username} (e.g., users/steven)
GetUser(context.Context, *GetUserRequest) (*User, error) GetUser(context.Context, *GetUserRequest) (*User, error)
...@@ -359,6 +374,9 @@ type UnimplementedUserServiceServer struct{} ...@@ -359,6 +374,9 @@ type UnimplementedUserServiceServer struct{}
func (UnimplementedUserServiceServer) ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) { func (UnimplementedUserServiceServer) ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListUsers not implemented") return nil, status.Error(codes.Unimplemented, "method ListUsers not implemented")
} }
func (UnimplementedUserServiceServer) BatchGetUsers(context.Context, *BatchGetUsersRequest) (*BatchGetUsersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method BatchGetUsers not implemented")
}
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*User, error) { func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*User, error) {
return nil, status.Error(codes.Unimplemented, "method GetUser not implemented") return nil, status.Error(codes.Unimplemented, "method GetUser not implemented")
} }
...@@ -455,6 +473,24 @@ func _UserService_ListUsers_Handler(srv interface{}, ctx context.Context, dec fu ...@@ -455,6 +473,24 @@ func _UserService_ListUsers_Handler(srv interface{}, ctx context.Context, dec fu
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _UserService_BatchGetUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BatchGetUsersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).BatchGetUsers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_BatchGetUsers_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).BatchGetUsers(ctx, req.(*BatchGetUsersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserRequest) in := new(GetUserRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
...@@ -808,6 +844,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{ ...@@ -808,6 +844,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListUsers", MethodName: "ListUsers",
Handler: _UserService_ListUsers_Handler, Handler: _UserService_ListUsers_Handler,
}, },
{
MethodName: "BatchGetUsers",
Handler: _UserService_BatchGetUsers_Handler,
},
{ {
MethodName: "GetUser", MethodName: "GetUser",
Handler: _UserService_GetUser_Handler, Handler: _UserService_GetUser_Handler,
......
...@@ -1977,6 +1977,31 @@ paths: ...@@ -1977,6 +1977,31 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Status' $ref: '#/components/schemas/Status'
/api/v1/users:batchGet:
post:
tags:
- UserService
description: BatchGetUsers returns active users by usernames.
operationId: UserService_BatchGetUsers
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BatchGetUsersRequest'
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/BatchGetUsersResponse'
default:
description: Default error response
content:
application/json:
schema:
$ref: '#/components/schemas/Status'
/api/v1/users:stats: /api/v1/users:stats:
get: get:
tags: tags:
...@@ -2050,6 +2075,20 @@ components: ...@@ -2050,6 +2075,20 @@ components:
type: array type: array
items: items:
type: string type: string
BatchGetUsersRequest:
type: object
properties:
usernames:
type: array
items:
type: string
BatchGetUsersResponse:
type: object
properties:
users:
type: array
items:
$ref: '#/components/schemas/User'
Color: Color:
type: object type: object
properties: properties:
...@@ -3192,6 +3231,11 @@ components: ...@@ -3192,6 +3231,11 @@ components:
description: |- description: |-
The sender of the notification. The sender of the notification.
Format: users/{user} Format: users/{user}
senderUser:
readOnly: true
allOf:
- $ref: '#/components/schemas/User'
description: The sender user details.
status: status:
enum: enum:
- STATUS_UNSPECIFIED - STATUS_UNSPECIFIED
...@@ -3210,6 +3254,7 @@ components: ...@@ -3210,6 +3254,7 @@ components:
enum: enum:
- TYPE_UNSPECIFIED - TYPE_UNSPECIFIED
- MEMO_COMMENT - MEMO_COMMENT
- MEMO_MENTION
type: string type: string
description: The type of the notification. description: The type of the notification.
format: enum format: enum
...@@ -3217,6 +3262,10 @@ components: ...@@ -3217,6 +3262,10 @@ components:
readOnly: true readOnly: true
allOf: allOf:
- $ref: '#/components/schemas/UserNotification_MemoCommentPayload' - $ref: '#/components/schemas/UserNotification_MemoCommentPayload'
memoMention:
readOnly: true
allOf:
- $ref: '#/components/schemas/UserNotification_MemoMentionPayload'
UserNotification_MemoCommentPayload: UserNotification_MemoCommentPayload:
type: object type: object
properties: properties:
...@@ -3230,6 +3279,31 @@ components: ...@@ -3230,6 +3279,31 @@ components:
description: |- description: |-
The name of related memo. The name of related memo.
Format: memos/{memo} Format: memos/{memo}
memoSnippet:
type: string
description: Preview text of the comment memo.
relatedMemoSnippet:
type: string
description: Preview text of the related memo.
UserNotification_MemoMentionPayload:
type: object
properties:
memo:
type: string
description: |-
The memo that contains the mention.
Format: memos/{memo}
relatedMemo:
type: string
description: |-
The related parent memo when the mention was created in a comment.
Format: memos/{memo}
memoSnippet:
type: string
description: Preview text of the memo that contains the mention.
relatedMemoSnippet:
type: string
description: Preview text of the related parent memo.
UserSetting: UserSetting:
type: object type: object
properties: properties:
......
...@@ -27,6 +27,8 @@ const ( ...@@ -27,6 +27,8 @@ const (
InboxMessage_TYPE_UNSPECIFIED InboxMessage_Type = 0 InboxMessage_TYPE_UNSPECIFIED InboxMessage_Type = 0
// Memo comment notification. // Memo comment notification.
InboxMessage_MEMO_COMMENT InboxMessage_Type = 1 InboxMessage_MEMO_COMMENT InboxMessage_Type = 1
// Memo mention notification.
InboxMessage_MEMO_MENTION InboxMessage_Type = 2
) )
// Enum value maps for InboxMessage_Type. // Enum value maps for InboxMessage_Type.
...@@ -34,10 +36,12 @@ var ( ...@@ -34,10 +36,12 @@ var (
InboxMessage_Type_name = map[int32]string{ InboxMessage_Type_name = map[int32]string{
0: "TYPE_UNSPECIFIED", 0: "TYPE_UNSPECIFIED",
1: "MEMO_COMMENT", 1: "MEMO_COMMENT",
2: "MEMO_MENTION",
} }
InboxMessage_Type_value = map[string]int32{ InboxMessage_Type_value = map[string]int32{
"TYPE_UNSPECIFIED": 0, "TYPE_UNSPECIFIED": 0,
"MEMO_COMMENT": 1, "MEMO_COMMENT": 1,
"MEMO_MENTION": 2,
} }
) )
...@@ -75,6 +79,7 @@ type InboxMessage struct { ...@@ -75,6 +79,7 @@ type InboxMessage struct {
// Types that are valid to be assigned to Payload: // Types that are valid to be assigned to Payload:
// //
// *InboxMessage_MemoComment // *InboxMessage_MemoComment
// *InboxMessage_MemoMention
Payload isInboxMessage_Payload `protobuf_oneof:"payload"` Payload isInboxMessage_Payload `protobuf_oneof:"payload"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
...@@ -133,6 +138,15 @@ func (x *InboxMessage) GetMemoComment() *InboxMessage_MemoCommentPayload { ...@@ -133,6 +138,15 @@ func (x *InboxMessage) GetMemoComment() *InboxMessage_MemoCommentPayload {
return nil return nil
} }
func (x *InboxMessage) GetMemoMention() *InboxMessage_MemoMentionPayload {
if x != nil {
if x, ok := x.Payload.(*InboxMessage_MemoMention); ok {
return x.MemoMention
}
}
return nil
}
type isInboxMessage_Payload interface { type isInboxMessage_Payload interface {
isInboxMessage_Payload() isInboxMessage_Payload()
} }
...@@ -141,8 +155,14 @@ type InboxMessage_MemoComment struct { ...@@ -141,8 +155,14 @@ type InboxMessage_MemoComment struct {
MemoComment *InboxMessage_MemoCommentPayload `protobuf:"bytes,2,opt,name=memo_comment,json=memoComment,proto3,oneof"` MemoComment *InboxMessage_MemoCommentPayload `protobuf:"bytes,2,opt,name=memo_comment,json=memoComment,proto3,oneof"`
} }
type InboxMessage_MemoMention struct {
MemoMention *InboxMessage_MemoMentionPayload `protobuf:"bytes,3,opt,name=memo_mention,json=memoMention,proto3,oneof"`
}
func (*InboxMessage_MemoComment) isInboxMessage_Payload() {} func (*InboxMessage_MemoComment) isInboxMessage_Payload() {}
func (*InboxMessage_MemoMention) isInboxMessage_Payload() {}
type InboxMessage_MemoCommentPayload struct { type InboxMessage_MemoCommentPayload struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
MemoId int32 `protobuf:"varint,1,opt,name=memo_id,json=memoId,proto3" json:"memo_id,omitempty"` MemoId int32 `protobuf:"varint,1,opt,name=memo_id,json=memoId,proto3" json:"memo_id,omitempty"`
...@@ -195,20 +215,77 @@ func (x *InboxMessage_MemoCommentPayload) GetRelatedMemoId() int32 { ...@@ -195,20 +215,77 @@ func (x *InboxMessage_MemoCommentPayload) GetRelatedMemoId() int32 {
return 0 return 0
} }
type InboxMessage_MemoMentionPayload struct {
state protoimpl.MessageState `protogen:"open.v1"`
MemoId int32 `protobuf:"varint,1,opt,name=memo_id,json=memoId,proto3" json:"memo_id,omitempty"`
RelatedMemoId int32 `protobuf:"varint,2,opt,name=related_memo_id,json=relatedMemoId,proto3" json:"related_memo_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *InboxMessage_MemoMentionPayload) Reset() {
*x = InboxMessage_MemoMentionPayload{}
mi := &file_store_inbox_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *InboxMessage_MemoMentionPayload) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*InboxMessage_MemoMentionPayload) ProtoMessage() {}
func (x *InboxMessage_MemoMentionPayload) ProtoReflect() protoreflect.Message {
mi := &file_store_inbox_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use InboxMessage_MemoMentionPayload.ProtoReflect.Descriptor instead.
func (*InboxMessage_MemoMentionPayload) Descriptor() ([]byte, []int) {
return file_store_inbox_proto_rawDescGZIP(), []int{0, 1}
}
func (x *InboxMessage_MemoMentionPayload) GetMemoId() int32 {
if x != nil {
return x.MemoId
}
return 0
}
func (x *InboxMessage_MemoMentionPayload) GetRelatedMemoId() int32 {
if x != nil {
return x.RelatedMemoId
}
return 0
}
var File_store_inbox_proto protoreflect.FileDescriptor var File_store_inbox_proto protoreflect.FileDescriptor
const file_store_inbox_proto_rawDesc = "" + const file_store_inbox_proto_rawDesc = "" +
"\n" + "\n" +
"\x11store/inbox.proto\x12\vmemos.store\"\xa7\x02\n" + "\x11store/inbox.proto\x12\vmemos.store\"\xe3\x03\n" +
"\fInboxMessage\x122\n" + "\fInboxMessage\x122\n" +
"\x04type\x18\x01 \x01(\x0e2\x1e.memos.store.InboxMessage.TypeR\x04type\x12Q\n" + "\x04type\x18\x01 \x01(\x0e2\x1e.memos.store.InboxMessage.TypeR\x04type\x12Q\n" +
"\fmemo_comment\x18\x02 \x01(\v2,.memos.store.InboxMessage.MemoCommentPayloadH\x00R\vmemoComment\x1aU\n" + "\fmemo_comment\x18\x02 \x01(\v2,.memos.store.InboxMessage.MemoCommentPayloadH\x00R\vmemoComment\x12Q\n" +
"\fmemo_mention\x18\x03 \x01(\v2,.memos.store.InboxMessage.MemoMentionPayloadH\x00R\vmemoMention\x1aU\n" +
"\x12MemoCommentPayload\x12\x17\n" + "\x12MemoCommentPayload\x12\x17\n" +
"\amemo_id\x18\x01 \x01(\x05R\x06memoId\x12&\n" + "\amemo_id\x18\x01 \x01(\x05R\x06memoId\x12&\n" +
"\x0frelated_memo_id\x18\x02 \x01(\x05R\rrelatedMemoId\".\n" + "\x0frelated_memo_id\x18\x02 \x01(\x05R\rrelatedMemoId\x1aU\n" +
"\x12MemoMentionPayload\x12\x17\n" +
"\amemo_id\x18\x01 \x01(\x05R\x06memoId\x12&\n" +
"\x0frelated_memo_id\x18\x02 \x01(\x05R\rrelatedMemoId\"@\n" +
"\x04Type\x12\x14\n" + "\x04Type\x12\x14\n" +
"\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" + "\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" +
"\fMEMO_COMMENT\x10\x01B\t\n" + "\fMEMO_COMMENT\x10\x01\x12\x10\n" +
"\fMEMO_MENTION\x10\x02B\t\n" +
"\apayloadB\x95\x01\n" + "\apayloadB\x95\x01\n" +
"\x0fcom.memos.storeB\n" + "\x0fcom.memos.storeB\n" +
"InboxProtoP\x01Z)github.com/usememos/memos/proto/gen/store\xa2\x02\x03MSX\xaa\x02\vMemos.Store\xca\x02\vMemos\\Store\xe2\x02\x17Memos\\Store\\GPBMetadata\xea\x02\fMemos::Storeb\x06proto3" "InboxProtoP\x01Z)github.com/usememos/memos/proto/gen/store\xa2\x02\x03MSX\xaa\x02\vMemos.Store\xca\x02\vMemos\\Store\xe2\x02\x17Memos\\Store\\GPBMetadata\xea\x02\fMemos::Storeb\x06proto3"
...@@ -226,20 +303,22 @@ func file_store_inbox_proto_rawDescGZIP() []byte { ...@@ -226,20 +303,22 @@ func file_store_inbox_proto_rawDescGZIP() []byte {
} }
var file_store_inbox_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_store_inbox_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_store_inbox_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_store_inbox_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_store_inbox_proto_goTypes = []any{ var file_store_inbox_proto_goTypes = []any{
(InboxMessage_Type)(0), // 0: memos.store.InboxMessage.Type (InboxMessage_Type)(0), // 0: memos.store.InboxMessage.Type
(*InboxMessage)(nil), // 1: memos.store.InboxMessage (*InboxMessage)(nil), // 1: memos.store.InboxMessage
(*InboxMessage_MemoCommentPayload)(nil), // 2: memos.store.InboxMessage.MemoCommentPayload (*InboxMessage_MemoCommentPayload)(nil), // 2: memos.store.InboxMessage.MemoCommentPayload
(*InboxMessage_MemoMentionPayload)(nil), // 3: memos.store.InboxMessage.MemoMentionPayload
} }
var file_store_inbox_proto_depIdxs = []int32{ var file_store_inbox_proto_depIdxs = []int32{
0, // 0: memos.store.InboxMessage.type:type_name -> memos.store.InboxMessage.Type 0, // 0: memos.store.InboxMessage.type:type_name -> memos.store.InboxMessage.Type
2, // 1: memos.store.InboxMessage.memo_comment:type_name -> memos.store.InboxMessage.MemoCommentPayload 2, // 1: memos.store.InboxMessage.memo_comment:type_name -> memos.store.InboxMessage.MemoCommentPayload
2, // [2:2] is the sub-list for method output_type 3, // 2: memos.store.InboxMessage.memo_mention:type_name -> memos.store.InboxMessage.MemoMentionPayload
2, // [2:2] is the sub-list for method input_type 3, // [3:3] is the sub-list for method output_type
2, // [2:2] is the sub-list for extension type_name 3, // [3:3] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension extendee 3, // [3:3] is the sub-list for extension type_name
0, // [0:2] is the sub-list for field type_name 3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
} }
func init() { file_store_inbox_proto_init() } func init() { file_store_inbox_proto_init() }
...@@ -249,6 +328,7 @@ func file_store_inbox_proto_init() { ...@@ -249,6 +328,7 @@ func file_store_inbox_proto_init() {
} }
file_store_inbox_proto_msgTypes[0].OneofWrappers = []any{ file_store_inbox_proto_msgTypes[0].OneofWrappers = []any{
(*InboxMessage_MemoComment)(nil), (*InboxMessage_MemoComment)(nil),
(*InboxMessage_MemoMention)(nil),
} }
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
...@@ -256,7 +336,7 @@ func file_store_inbox_proto_init() { ...@@ -256,7 +336,7 @@ func file_store_inbox_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_store_inbox_proto_rawDesc), len(file_store_inbox_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_store_inbox_proto_rawDesc), len(file_store_inbox_proto_rawDesc)),
NumEnums: 1, NumEnums: 1,
NumMessages: 2, NumMessages: 3,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },
......
...@@ -10,15 +10,23 @@ message InboxMessage { ...@@ -10,15 +10,23 @@ message InboxMessage {
int32 related_memo_id = 2; int32 related_memo_id = 2;
} }
message MemoMentionPayload {
int32 memo_id = 1;
int32 related_memo_id = 2;
}
// The type of the inbox message. // The type of the inbox message.
Type type = 1; Type type = 1;
oneof payload { oneof payload {
MemoCommentPayload memo_comment = 2; MemoCommentPayload memo_comment = 2;
MemoMentionPayload memo_mention = 3;
} }
enum Type { enum Type {
TYPE_UNSPECIFIED = 0; TYPE_UNSPECIFIED = 0;
// Memo comment notification. // Memo comment notification.
MEMO_COMMENT = 1; MEMO_COMMENT = 1;
// Memo mention notification.
MEMO_MENTION = 2;
} }
} }
...@@ -20,10 +20,10 @@ var PublicMethods = map[string]struct{}{ ...@@ -20,10 +20,10 @@ var PublicMethods = map[string]struct{}{
// User Service - public user profiles and stats // User Service - public user profiles and stats
"/memos.api.v1.UserService/CreateUser": {}, // Allow first user registration "/memos.api.v1.UserService/CreateUser": {}, // Allow first user registration
"/memos.api.v1.UserService/GetUser": {}, "/memos.api.v1.UserService/GetUser": {},
"/memos.api.v1.UserService/BatchGetUsers": {},
"/memos.api.v1.UserService/GetUserAvatar": {}, "/memos.api.v1.UserService/GetUserAvatar": {},
"/memos.api.v1.UserService/GetUserStats": {}, "/memos.api.v1.UserService/GetUserStats": {},
"/memos.api.v1.UserService/ListAllUserStats": {}, "/memos.api.v1.UserService/ListAllUserStats": {},
"/memos.api.v1.UserService/SearchUsers": {},
// Identity Provider Service - SSO buttons on login page // Identity Provider Service - SSO buttons on login page
"/memos.api.v1.IdentityProviderService/ListIdentityProviders": {}, "/memos.api.v1.IdentityProviderService/ListIdentityProviders": {},
......
...@@ -18,10 +18,10 @@ func TestPublicMethodsArePublic(t *testing.T) { ...@@ -18,10 +18,10 @@ func TestPublicMethodsArePublic(t *testing.T) {
// User Service // User Service
"/memos.api.v1.UserService/CreateUser", "/memos.api.v1.UserService/CreateUser",
"/memos.api.v1.UserService/GetUser", "/memos.api.v1.UserService/GetUser",
"/memos.api.v1.UserService/BatchGetUsers",
"/memos.api.v1.UserService/GetUserAvatar", "/memos.api.v1.UserService/GetUserAvatar",
"/memos.api.v1.UserService/GetUserStats", "/memos.api.v1.UserService/GetUserStats",
"/memos.api.v1.UserService/ListAllUserStats", "/memos.api.v1.UserService/ListAllUserStats",
"/memos.api.v1.UserService/SearchUsers",
// Identity Provider Service // Identity Provider Service
"/memos.api.v1.IdentityProviderService/ListIdentityProviders", "/memos.api.v1.IdentityProviderService/ListIdentityProviders",
// Memo Service // Memo Service
......
...@@ -79,6 +79,14 @@ func (s *ConnectServiceHandler) ListUsers(ctx context.Context, req *connect.Requ ...@@ -79,6 +79,14 @@ func (s *ConnectServiceHandler) ListUsers(ctx context.Context, req *connect.Requ
return connect.NewResponse(resp), nil return connect.NewResponse(resp), nil
} }
func (s *ConnectServiceHandler) BatchGetUsers(ctx context.Context, req *connect.Request[v1pb.BatchGetUsersRequest]) (*connect.Response[v1pb.BatchGetUsersResponse], error) {
resp, err := s.APIV1Service.BatchGetUsers(ctx, req.Msg)
if err != nil {
return nil, convertGRPCError(err)
}
return connect.NewResponse(resp), nil
}
func (s *ConnectServiceHandler) GetUser(ctx context.Context, req *connect.Request[v1pb.GetUserRequest]) (*connect.Response[v1pb.User], error) { func (s *ConnectServiceHandler) GetUser(ctx context.Context, req *connect.Request[v1pb.GetUserRequest]) (*connect.Response[v1pb.User], error) {
resp, err := s.APIV1Service.GetUser(ctx, req.Msg) resp, err := s.APIV1Service.GetUser(ctx, req.Msg)
if err != nil { if err != nil {
......
package v1
import (
"context"
"github.com/usememos/memos/store"
)
func (s *APIV1Service) listMemosByID(ctx context.Context, memoIDs []int32) (map[int32]*store.Memo, error) {
if len(memoIDs) == 0 {
return map[int32]*store.Memo{}, nil
}
uniqueMemoIDs := make([]int32, 0, len(memoIDs))
seenMemoIDs := make(map[int32]struct{}, len(memoIDs))
for _, memoID := range memoIDs {
if _, seen := seenMemoIDs[memoID]; seen {
continue
}
seenMemoIDs[memoID] = struct{}{}
uniqueMemoIDs = append(uniqueMemoIDs, memoID)
}
memos, err := s.Store.ListMemos(ctx, &store.FindMemo{IDList: uniqueMemoIDs})
if err != nil {
return nil, err
}
memosByID := make(map[int32]*store.Memo, len(memos))
for _, memo := range memos {
memosByID[memo.ID] = memo
}
return memosByID, nil
}
package v1
import (
"context"
"log/slog"
"github.com/pkg/errors"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
// suppressMentionKey is a context key used to suppress mention notification side effects
// when CreateMemo is called internally from CreateMemoComment.
type suppressMentionKey struct{}
func withSuppressMentionNotifications(ctx context.Context) context.Context {
return context.WithValue(ctx, suppressMentionKey{}, true)
}
func isMentionNotificationSuppressed(ctx context.Context) bool {
v, ok := ctx.Value(suppressMentionKey{}).(bool)
return ok && v
}
func (s *APIV1Service) resolveMentionTargets(ctx context.Context, content string) (map[int32]*store.User, error) {
targets := make(map[int32]*store.User)
if content == "" {
return targets, nil
}
data, err := s.MarkdownService.ExtractAll([]byte(content))
if err != nil {
return nil, errors.Wrap(err, "failed to extract mentions")
}
if len(data.Mentions) == 0 {
return targets, nil
}
normal := store.Normal
users, err := s.Store.ListUsers(ctx, &store.FindUser{
UsernameList: data.Mentions,
RowStatus: &normal,
})
if err != nil {
return nil, errors.Wrap(err, "failed to resolve mention users")
}
for _, user := range users {
targets[user.ID] = user
}
return targets, nil
}
func canUserAccessMentionContext(target *store.User, memo *store.Memo, relatedMemo *store.Memo) bool {
if target == nil || memo == nil {
return false
}
if relatedMemo != nil {
if relatedMemo.Visibility == store.Private && target.ID != relatedMemo.CreatorID {
return false
}
}
if memo.Visibility == store.Private && target.ID != memo.CreatorID {
return false
}
return true
}
func shouldSkipMentionInbox(target *store.User, memo *store.Memo, relatedMemo *store.Memo) bool {
if target == nil || memo == nil {
return true
}
if target.ID == memo.CreatorID {
return true
}
// Comment creation already generates a memo-comment inbox item for the parent creator.
if relatedMemo != nil && target.ID == relatedMemo.CreatorID && memo.Visibility != store.Private && memo.CreatorID != relatedMemo.CreatorID {
return true
}
return !canUserAccessMentionContext(target, memo, relatedMemo)
}
func (s *APIV1Service) dispatchMemoMentionNotifications(ctx context.Context, memo *store.Memo, relatedMemo *store.Memo, previousContent string) error {
if memo == nil {
return nil
}
currentTargets, err := s.resolveMentionTargets(ctx, memo.Content)
if err != nil {
return err
}
if len(currentTargets) == 0 {
return nil
}
previousTargets, err := s.resolveMentionTargets(ctx, previousContent)
if err != nil {
return err
}
for userID, target := range currentTargets {
if _, exists := previousTargets[userID]; exists {
continue
}
if shouldSkipMentionInbox(target, memo, relatedMemo) {
continue
}
payload := &storepb.InboxMessage_MemoMentionPayload{
MemoId: memo.ID,
}
if relatedMemo != nil {
payload.RelatedMemoId = relatedMemo.ID
}
if _, err := s.Store.CreateInbox(ctx, &store.Inbox{
SenderID: memo.CreatorID,
ReceiverID: target.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{
Type: storepb.InboxMessage_MEMO_MENTION,
Payload: &storepb.InboxMessage_MemoMention{
MemoMention: payload,
},
},
}); err != nil {
return errors.Wrap(err, "failed to create mention inbox")
}
}
return nil
}
func (s *APIV1Service) dispatchMemoMentionNotificationsBestEffort(ctx context.Context, memo *store.Memo, relatedMemo *store.Memo, previousContent string) {
if err := s.dispatchMemoMentionNotifications(ctx, memo, relatedMemo, previousContent); err != nil {
slog.Warn("Failed to dispatch memo mention notifications", slog.Any("err", err), slog.Int64("memo_id", int64(memo.ID)))
}
}
...@@ -89,7 +89,7 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR ...@@ -89,7 +89,7 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR
if len(create.Content) > contentLengthLimit { if len(create.Content) > contentLengthLimit {
return nil, status.Errorf(codes.InvalidArgument, "content too long (max %d characters)", contentLengthLimit) return nil, status.Errorf(codes.InvalidArgument, "content too long (max %d characters)", contentLengthLimit)
} }
if err := memopayload.RebuildMemoPayload(create, s.MarkdownService); err != nil { if err := memopayload.RebuildMemoPayload(ctx, create, s.MarkdownService); err != nil {
return nil, status.Errorf(codes.Internal, "failed to rebuild memo payload: %v", err) return nil, status.Errorf(codes.Internal, "failed to rebuild memo payload: %v", err)
} }
if request.Memo.Location != nil { if request.Memo.Location != nil {
...@@ -160,6 +160,10 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR ...@@ -160,6 +160,10 @@ func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoR
}) })
} }
if !isMentionNotificationSuppressed(ctx) {
s.dispatchMemoMentionNotificationsBestEffort(ctx, memo, nil, "")
}
return memoMessage, nil return memoMessage, nil
} }
...@@ -433,8 +437,12 @@ func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoR ...@@ -433,8 +437,12 @@ func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoR
update := &store.UpdateMemo{ update := &store.UpdateMemo{
ID: memo.ID, ID: memo.ID,
} }
var previousContent string
contentUpdated := false
for _, path := range request.UpdateMask.Paths { for _, path := range request.UpdateMask.Paths {
if path == "content" { if path == "content" {
contentUpdated = true
previousContent = memo.Content
contentLengthLimit, err := s.getContentLengthLimit(ctx) contentLengthLimit, err := s.getContentLengthLimit(ctx)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get content length limit") return nil, status.Errorf(codes.Internal, "failed to get content length limit")
...@@ -443,7 +451,7 @@ func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoR ...@@ -443,7 +451,7 @@ func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoR
return nil, status.Errorf(codes.InvalidArgument, "content too long (max %d characters)", contentLengthLimit) return nil, status.Errorf(codes.InvalidArgument, "content too long (max %d characters)", contentLengthLimit)
} }
memo.Content = request.Memo.Content memo.Content = request.Memo.Content
if err := memopayload.RebuildMemoPayload(memo, s.MarkdownService); err != nil { if err := memopayload.RebuildMemoPayload(ctx, memo, s.MarkdownService); err != nil {
return nil, status.Errorf(codes.Internal, "failed to rebuild memo payload: %v", err) return nil, status.Errorf(codes.Internal, "failed to rebuild memo payload: %v", err)
} }
update.Content = &memo.Content update.Content = &memo.Content
...@@ -505,6 +513,9 @@ func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoR ...@@ -505,6 +513,9 @@ func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoR
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to build updated memo state") return nil, errors.Wrap(err, "failed to build updated memo state")
} }
if contentUpdated {
s.dispatchMemoMentionNotificationsBestEffort(ctx, memo, parentMemo, previousContent)
}
s.dispatchMemoUpdatedSideEffects(ctx, memo, parentMemo, memoMessage) s.dispatchMemoUpdatedSideEffects(ctx, memo, parentMemo, memoMessage)
return memoMessage, nil return memoMessage, nil
...@@ -614,7 +625,7 @@ func (s *APIV1Service) CreateMemoComment(ctx context.Context, request *v1pb.Crea ...@@ -614,7 +625,7 @@ func (s *APIV1Service) CreateMemoComment(ctx context.Context, request *v1pb.Crea
// Create the memo comment first; suppress the generic memo.created SSE event // Create the memo comment first; suppress the generic memo.created SSE event
// since CreateMemoComment broadcasts memo.comment.created for the parent instead. // since CreateMemoComment broadcasts memo.comment.created for the parent instead.
memoComment, err := s.CreateMemo(withSuppressSSE(ctx), &v1pb.CreateMemoRequest{ memoComment, err := s.CreateMemo(withSuppressMentionNotifications(withSuppressSSE(ctx)), &v1pb.CreateMemoRequest{
Memo: request.Comment, Memo: request.Comment,
MemoId: request.CommentId, MemoId: request.CommentId,
}) })
...@@ -670,6 +681,8 @@ func (s *APIV1Service) CreateMemoComment(ctx context.Context, request *v1pb.Crea ...@@ -670,6 +681,8 @@ func (s *APIV1Service) CreateMemoComment(ctx context.Context, request *v1pb.Crea
slog.Warn("Failed to dispatch memo comment created webhook", slog.Any("err", err)) slog.Warn("Failed to dispatch memo comment created webhook", slog.Any("err", err))
} }
s.dispatchMemoMentionNotificationsBestEffort(ctx, memo, relatedMemo, "")
// Broadcast live refresh event for the parent memo so subscribers see the new comment. // Broadcast live refresh event for the parent memo so subscribers see the new comment.
s.SSEHub.Broadcast(&SSEEvent{ s.SSEHub.Broadcast(&SSEEvent{
Type: SSEEventMemoCommentCreated, Type: SSEEventMemoCommentCreated,
......
...@@ -42,6 +42,7 @@ func NewTestService(t *testing.T) *TestService { ...@@ -42,6 +42,7 @@ func NewTestService(t *testing.T) *TestService {
secret := "test-secret" secret := "test-secret"
markdownService := markdown.NewService( markdownService := markdown.NewService(
markdown.WithTagExtension(), markdown.WithTagExtension(),
markdown.WithMentionExtension(),
) )
service := &apiv1.APIV1Service{ service := &apiv1.APIV1Service{
Secret: secret, Secret: secret,
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/fieldmaskpb"
apiv1 "github.com/usememos/memos/proto/gen/api/v1" apiv1 "github.com/usememos/memos/proto/gen/api/v1"
storepb "github.com/usememos/memos/proto/gen/store" storepb "github.com/usememos/memos/proto/gen/store"
...@@ -51,10 +52,14 @@ func TestListUserNotificationsIncludesMemoCommentPayload(t *testing.T) { ...@@ -51,10 +52,14 @@ func TestListUserNotificationsIncludesMemoCommentPayload(t *testing.T) {
notification := resp.Notifications[0] notification := resp.Notifications[0]
require.Contains(t, notification.Name, fmt.Sprintf("users/%s/notifications/", owner.Username)) require.Contains(t, notification.Name, fmt.Sprintf("users/%s/notifications/", owner.Username))
require.Equal(t, fmt.Sprintf("users/%s", commenter.Username), notification.Sender) require.Equal(t, fmt.Sprintf("users/%s", commenter.Username), notification.Sender)
require.NotNil(t, notification.SenderUser)
require.Equal(t, commenter.Username, notification.SenderUser.Username)
require.Equal(t, apiv1.UserNotification_MEMO_COMMENT, notification.Type) require.Equal(t, apiv1.UserNotification_MEMO_COMMENT, notification.Type)
require.NotNil(t, notification.GetMemoComment()) require.NotNil(t, notification.GetMemoComment())
require.Equal(t, comment.Name, notification.GetMemoComment().Memo) require.Equal(t, comment.Name, notification.GetMemoComment().Memo)
require.Equal(t, memo.Name, notification.GetMemoComment().RelatedMemo) require.Equal(t, memo.Name, notification.GetMemoComment().RelatedMemo)
require.Equal(t, "Comment content", notification.GetMemoComment().MemoSnippet)
require.Equal(t, "Base memo", notification.GetMemoComment().RelatedMemoSnippet)
} }
func TestListUserNotificationsStoresMemoCommentPayloadInInbox(t *testing.T) { func TestListUserNotificationsStoresMemoCommentPayloadInInbox(t *testing.T) {
...@@ -199,3 +204,142 @@ func TestListUserNotificationsRejectsNumericParent(t *testing.T) { ...@@ -199,3 +204,142 @@ func TestListUserNotificationsRejectsNumericParent(t *testing.T) {
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), "invalid user name") require.Contains(t, err.Error(), "invalid user name")
} }
func TestListUserNotificationsIncludesMemoMentionPayload(t *testing.T) {
ctx := context.Background()
ts := NewTestService(t)
defer ts.Cleanup()
author, err := ts.CreateRegularUser(ctx, "mention-author")
require.NoError(t, err)
authorCtx := ts.CreateUserContext(ctx, author.ID)
target, err := ts.CreateRegularUser(ctx, "mention-target")
require.NoError(t, err)
targetCtx := ts.CreateUserContext(ctx, target.ID)
memo, err := ts.Service.CreateMemo(authorCtx, &apiv1.CreateMemoRequest{
Memo: &apiv1.Memo{
Content: fmt.Sprintf("Hello @%s", target.Username),
Visibility: apiv1.Visibility_PUBLIC,
},
})
require.NoError(t, err)
resp, err := ts.Service.ListUserNotifications(targetCtx, &apiv1.ListUserNotificationsRequest{
Parent: fmt.Sprintf("users/%s", target.Username),
})
require.NoError(t, err)
require.Len(t, resp.Notifications, 1)
require.Equal(t, apiv1.UserNotification_MEMO_MENTION, resp.Notifications[0].Type)
require.NotNil(t, resp.Notifications[0].GetMemoMention())
require.Equal(t, memo.Name, resp.Notifications[0].GetMemoMention().Memo)
require.Empty(t, resp.Notifications[0].GetMemoMention().RelatedMemo)
require.Equal(t, author.Username, resp.Notifications[0].SenderUser.Username)
require.Equal(t, "Hello", resp.Notifications[0].GetMemoMention().MemoSnippet)
}
func TestCreateMemoCommentMentionDoesNotDuplicateOwnerNotification(t *testing.T) {
ctx := context.Background()
ts := NewTestService(t)
defer ts.Cleanup()
owner, err := ts.CreateRegularUser(ctx, "mention-owner")
require.NoError(t, err)
ownerCtx := ts.CreateUserContext(ctx, owner.ID)
commenter, err := ts.CreateRegularUser(ctx, "mention-commenter")
require.NoError(t, err)
commenterCtx := ts.CreateUserContext(ctx, commenter.ID)
memo, err := ts.Service.CreateMemo(ownerCtx, &apiv1.CreateMemoRequest{
Memo: &apiv1.Memo{
Content: "Base memo",
Visibility: apiv1.Visibility_PUBLIC,
},
})
require.NoError(t, err)
_, err = ts.Service.CreateMemoComment(commenterCtx, &apiv1.CreateMemoCommentRequest{
Name: memo.Name,
Comment: &apiv1.Memo{
Content: fmt.Sprintf("Hi @%s", owner.Username),
Visibility: apiv1.Visibility_PUBLIC,
},
})
require.NoError(t, err)
resp, err := ts.Service.ListUserNotifications(ownerCtx, &apiv1.ListUserNotificationsRequest{
Parent: fmt.Sprintf("users/%s", owner.Username),
})
require.NoError(t, err)
require.Len(t, resp.Notifications, 1)
require.Equal(t, apiv1.UserNotification_MEMO_COMMENT, resp.Notifications[0].Type)
}
func TestUpdateMemoMentionOnlyNotifiesNewTargets(t *testing.T) {
ctx := context.Background()
ts := NewTestService(t)
defer ts.Cleanup()
author, err := ts.CreateRegularUser(ctx, "mention-update-author")
require.NoError(t, err)
authorCtx := ts.CreateUserContext(ctx, author.ID)
firstTarget, err := ts.CreateRegularUser(ctx, "mention-update-first")
require.NoError(t, err)
firstTargetCtx := ts.CreateUserContext(ctx, firstTarget.ID)
secondTarget, err := ts.CreateRegularUser(ctx, "mention-update-second")
require.NoError(t, err)
secondTargetCtx := ts.CreateUserContext(ctx, secondTarget.ID)
memo, err := ts.Service.CreateMemo(authorCtx, &apiv1.CreateMemoRequest{
Memo: &apiv1.Memo{
Content: "",
Visibility: apiv1.Visibility_PUBLIC,
},
})
require.NoError(t, err)
updatedMemo, err := ts.Service.UpdateMemo(authorCtx, &apiv1.UpdateMemoRequest{
Memo: &apiv1.Memo{
Name: memo.Name,
Content: fmt.Sprintf("Hello @%s", firstTarget.Username),
Visibility: apiv1.Visibility_PUBLIC,
},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"content"}},
})
require.NoError(t, err)
firstResp, err := ts.Service.ListUserNotifications(firstTargetCtx, &apiv1.ListUserNotificationsRequest{
Parent: fmt.Sprintf("users/%s", firstTarget.Username),
})
require.NoError(t, err)
require.Len(t, firstResp.Notifications, 1)
require.Equal(t, apiv1.UserNotification_MEMO_MENTION, firstResp.Notifications[0].Type)
require.Equal(t, updatedMemo.Name, firstResp.Notifications[0].GetMemoMention().Memo)
_, err = ts.Service.UpdateMemo(authorCtx, &apiv1.UpdateMemoRequest{
Memo: &apiv1.Memo{
Name: memo.Name,
Content: fmt.Sprintf("Hello again @%s and @%s", firstTarget.Username, secondTarget.Username),
Visibility: apiv1.Visibility_PUBLIC,
},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"content"}},
})
require.NoError(t, err)
firstResp, err = ts.Service.ListUserNotifications(firstTargetCtx, &apiv1.ListUserNotificationsRequest{
Parent: fmt.Sprintf("users/%s", firstTarget.Username),
})
require.NoError(t, err)
require.Len(t, firstResp.Notifications, 1)
secondResp, err := ts.Service.ListUserNotifications(secondTargetCtx, &apiv1.ListUserNotificationsRequest{
Parent: fmt.Sprintf("users/%s", secondTarget.Username),
})
require.NoError(t, err)
require.Len(t, secondResp.Notifications, 1)
require.Equal(t, apiv1.UserNotification_MEMO_MENTION, secondResp.Notifications[0].Type)
}
package test
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/require"
apiv1 "github.com/usememos/memos/proto/gen/api/v1"
)
func TestBatchGetUsersReturnsExactUsernamesWithoutAuthentication(t *testing.T) {
ctx := context.Background()
ts := NewTestService(t)
defer ts.Cleanup()
_, err := ts.CreateRegularUser(ctx, "batch-alpha")
require.NoError(t, err)
_, err = ts.CreateRegularUser(ctx, "batch-beta")
require.NoError(t, err)
resp, err := ts.Service.BatchGetUsers(ctx, &apiv1.BatchGetUsersRequest{
Usernames: []string{"batch-alpha", "batch-beta", "missing-user", "batch-alpha"},
})
require.NoError(t, err)
require.Len(t, resp.Users, 2)
got := map[string]struct{}{}
for _, user := range resp.Users {
got[user.Username] = struct{}{}
}
_, ok := got["batch-alpha"]
require.True(t, ok)
_, ok = got["batch-beta"]
require.True(t, ok)
}
func TestBatchGetUsersRejectsTooManyUsernames(t *testing.T) {
ctx := context.Background()
ts := NewTestService(t)
defer ts.Cleanup()
usernames := make([]string, 0, 101)
for i := range 101 {
usernames = append(usernames, fmt.Sprintf("user-%d", i))
}
_, err := ts.Service.BatchGetUsers(ctx, &apiv1.BatchGetUsersRequest{
Usernames: usernames,
})
require.Error(t, err)
require.Contains(t, err.Error(), "too many usernames")
}
...@@ -29,6 +29,8 @@ import ( ...@@ -29,6 +29,8 @@ import (
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
) )
const maxBatchGetUsers = 100
func (s *APIV1Service) ListUsers(ctx context.Context, request *v1pb.ListUsersRequest) (*v1pb.ListUsersResponse, error) { func (s *APIV1Service) ListUsers(ctx context.Context, request *v1pb.ListUsersRequest) (*v1pb.ListUsersResponse, error) {
currentUser, err := s.fetchCurrentUser(ctx) currentUser, err := s.fetchCurrentUser(ctx)
if err != nil { if err != nil {
...@@ -70,6 +72,56 @@ func (s *APIV1Service) ListUsers(ctx context.Context, request *v1pb.ListUsersReq ...@@ -70,6 +72,56 @@ func (s *APIV1Service) ListUsers(ctx context.Context, request *v1pb.ListUsersReq
return response, nil return response, nil
} }
func normalizeBatchUsernames(usernames []string) []string {
uniqueUsernames := make([]string, 0, len(usernames))
seen := make(map[string]struct{}, len(usernames))
for _, username := range usernames {
username = strings.TrimSpace(strings.ToLower(username))
if username == "" || !base.UIDMatcher.MatchString(username) {
continue
}
if _, ok := seen[username]; ok {
continue
}
seen[username] = struct{}{}
uniqueUsernames = append(uniqueUsernames, username)
}
return uniqueUsernames
}
func (s *APIV1Service) BatchGetUsers(ctx context.Context, request *v1pb.BatchGetUsersRequest) (*v1pb.BatchGetUsersResponse, error) {
if len(request.Usernames) == 0 {
return &v1pb.BatchGetUsersResponse{Users: []*v1pb.User{}}, nil
}
uniqueUsernames := normalizeBatchUsernames(request.Usernames)
if len(uniqueUsernames) > maxBatchGetUsers {
return nil, status.Errorf(codes.InvalidArgument, "too many usernames (max %d)", maxBatchGetUsers)
}
if len(uniqueUsernames) == 0 {
return &v1pb.BatchGetUsersResponse{Users: []*v1pb.User{}}, nil
}
normal := store.Normal
users, err := s.Store.ListUsers(ctx, &store.FindUser{
UsernameList: uniqueUsernames,
RowStatus: &normal,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
}
currentUser, _ := s.fetchCurrentUser(ctx)
response := &v1pb.BatchGetUsersResponse{
Users: make([]*v1pb.User, 0, len(users)),
}
for _, user := range users {
response.Users = append(response.Users, convertUserFromStore(user, currentUser))
}
return response, nil
}
func (s *APIV1Service) GetUser(ctx context.Context, request *v1pb.GetUserRequest) (*v1pb.User, error) { func (s *APIV1Service) GetUser(ctx context.Context, request *v1pb.GetUserRequest) (*v1pb.User, error) {
user, err := ResolveUserByName(ctx, s.Store, request.Name) user, err := ResolveUserByName(ctx, s.Store, request.Name)
if err != nil { if err != nil {
...@@ -1269,12 +1321,9 @@ func (s *APIV1Service) ListUserNotifications(ctx context.Context, request *v1pb. ...@@ -1269,12 +1321,9 @@ func (s *APIV1Service) ListUserNotifications(ctx context.Context, request *v1pb.
return nil, status.Errorf(codes.PermissionDenied, "permission denied") return nil, status.Errorf(codes.PermissionDenied, "permission denied")
} }
// Fetch inbox items from storage // Fetch inbox items from storage.
// Filter at database level to only include MEMO_COMMENT notifications (ignore legacy VERSION_UPDATE entries)
memoCommentType := storepb.InboxMessage_MEMO_COMMENT
inboxes, err := s.Store.ListInboxes(ctx, &store.FindInbox{ inboxes, err := s.Store.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &userID, ReceiverID: &userID,
MessageType: &memoCommentType,
}) })
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list inboxes: %v", err) return nil, status.Errorf(codes.Internal, "failed to list inboxes: %v", err)
...@@ -1289,10 +1338,14 @@ func (s *APIV1Service) ListUserNotifications(ctx context.Context, request *v1pb. ...@@ -1289,10 +1338,14 @@ func (s *APIV1Service) ListUserNotifications(ctx context.Context, request *v1pb.
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list notification users: %v", err) return nil, status.Errorf(codes.Internal, "failed to list notification users: %v", err)
} }
memosByID, err := s.listMemosByID(ctx, collectInboxMemoIDs(inboxes))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list notification memos: %v", err)
}
notifications := []*v1pb.UserNotification{} notifications := []*v1pb.UserNotification{}
for _, inbox := range inboxes { for _, inbox := range inboxes {
notification, err := s.convertInboxToUserNotificationWithUsers(ctx, inbox, usersByID) notification, err := s.convertInboxToUserNotificationWithUsersAndMemos(inbox, currentUser, usersByID, memosByID)
if err != nil { if err != nil {
if status.Code(err) == codes.NotFound { if status.Code(err) == codes.NotFound {
slog.Warn("Skipping notification with missing user", slog.Warn("Skipping notification with missing user",
...@@ -1304,6 +1357,9 @@ func (s *APIV1Service) ListUserNotifications(ctx context.Context, request *v1pb. ...@@ -1304,6 +1357,9 @@ func (s *APIV1Service) ListUserNotifications(ctx context.Context, request *v1pb.
} }
return nil, status.Errorf(codes.Internal, "failed to convert inbox: %v", err) return nil, status.Errorf(codes.Internal, "failed to convert inbox: %v", err)
} }
if notification.Type == v1pb.UserNotification_TYPE_UNSPECIFIED {
continue
}
notifications = append(notifications, notification) notifications = append(notifications, notification)
} }
...@@ -1379,7 +1435,7 @@ func (s *APIV1Service) UpdateUserNotification(ctx context.Context, request *v1pb ...@@ -1379,7 +1435,7 @@ func (s *APIV1Service) UpdateUserNotification(ctx context.Context, request *v1pb
return nil, status.Errorf(codes.Internal, "failed to update inbox: %v", err) return nil, status.Errorf(codes.Internal, "failed to update inbox: %v", err)
} }
notification, err := s.convertInboxToUserNotification(ctx, updatedInbox) notification, err := s.convertInboxToUserNotification(ctx, updatedInbox, currentUser)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert inbox: %v", err) return nil, status.Errorf(codes.Internal, "failed to convert inbox: %v", err)
} }
...@@ -1432,15 +1488,43 @@ func (s *APIV1Service) DeleteUserNotification(ctx context.Context, request *v1pb ...@@ -1432,15 +1488,43 @@ func (s *APIV1Service) DeleteUserNotification(ctx context.Context, request *v1pb
// convertInboxToUserNotification converts a storage-layer inbox to an API notification. // convertInboxToUserNotification converts a storage-layer inbox to an API notification.
// This handles the mapping between the internal inbox representation and the public API. // This handles the mapping between the internal inbox representation and the public API.
func (s *APIV1Service) convertInboxToUserNotification(ctx context.Context, inbox *store.Inbox) (*v1pb.UserNotification, error) { func (s *APIV1Service) convertInboxToUserNotification(ctx context.Context, inbox *store.Inbox, viewer *store.User) (*v1pb.UserNotification, error) {
usersByID, err := s.listUsersByID(ctx, []int32{inbox.ReceiverID, inbox.SenderID}) usersByID, err := s.listUsersByID(ctx, []int32{inbox.ReceiverID, inbox.SenderID})
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list notification users: %v", err) return nil, status.Errorf(codes.Internal, "failed to list notification users: %v", err)
} }
return s.convertInboxToUserNotificationWithUsers(ctx, inbox, usersByID) memosByID, err := s.listMemosByID(ctx, collectInboxMemoIDs([]*store.Inbox{inbox}))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list notification memos: %v", err)
}
return s.convertInboxToUserNotificationWithUsersAndMemos(inbox, viewer, usersByID, memosByID)
} }
func (s *APIV1Service) convertInboxToUserNotificationWithUsers(ctx context.Context, inbox *store.Inbox, usersByID map[int32]*store.User) (*v1pb.UserNotification, error) { func collectInboxMemoIDs(inboxes []*store.Inbox) []int32 {
memoIDs := make([]int32, 0, len(inboxes)*2)
for _, inbox := range inboxes {
if inbox == nil || inbox.Message == nil {
continue
}
switch inbox.Message.Type {
case storepb.InboxMessage_MEMO_COMMENT:
payload := inbox.Message.GetMemoComment()
if payload != nil {
memoIDs = append(memoIDs, payload.MemoId, payload.RelatedMemoId)
}
case storepb.InboxMessage_MEMO_MENTION:
payload := inbox.Message.GetMemoMention()
if payload != nil {
memoIDs = append(memoIDs, payload.MemoId, payload.RelatedMemoId)
}
default:
// Ignore notification types without memo references.
}
}
return memoIDs
}
func (s *APIV1Service) convertInboxToUserNotificationWithUsersAndMemos(inbox *store.Inbox, viewer *store.User, usersByID map[int32]*store.User, memosByID map[int32]*store.Memo) (*v1pb.UserNotification, error) {
receiver := usersByID[inbox.ReceiverID] receiver := usersByID[inbox.ReceiverID]
if receiver == nil { if receiver == nil {
return nil, status.Errorf(codes.NotFound, "notification receiver not found") return nil, status.Errorf(codes.NotFound, "notification receiver not found")
...@@ -1453,6 +1537,7 @@ func (s *APIV1Service) convertInboxToUserNotificationWithUsers(ctx context.Conte ...@@ -1453,6 +1537,7 @@ func (s *APIV1Service) convertInboxToUserNotificationWithUsers(ctx context.Conte
notification := &v1pb.UserNotification{ notification := &v1pb.UserNotification{
Name: fmt.Sprintf("%s/notifications/%d", BuildUserName(receiver.Username), inbox.ID), Name: fmt.Sprintf("%s/notifications/%d", BuildUserName(receiver.Username), inbox.ID),
Sender: BuildUserName(sender.Username), Sender: BuildUserName(sender.Username),
SenderUser: convertUserFromStore(sender, viewer),
CreateTime: timestamppb.New(time.Unix(inbox.CreatedTs, 0)), CreateTime: timestamppb.New(time.Unix(inbox.CreatedTs, 0)),
} }
...@@ -1471,11 +1556,7 @@ func (s *APIV1Service) convertInboxToUserNotificationWithUsers(ctx context.Conte ...@@ -1471,11 +1556,7 @@ func (s *APIV1Service) convertInboxToUserNotificationWithUsers(ctx context.Conte
switch inbox.Message.Type { switch inbox.Message.Type {
case storepb.InboxMessage_MEMO_COMMENT: case storepb.InboxMessage_MEMO_COMMENT:
notification.Type = v1pb.UserNotification_MEMO_COMMENT notification.Type = v1pb.UserNotification_MEMO_COMMENT
default: payload, err := s.convertMemoCommentNotificationPayload(viewer, inbox.Message, memosByID)
notification.Type = v1pb.UserNotification_TYPE_UNSPECIFIED
}
payload, err := s.convertUserNotificationPayload(ctx, inbox.Message)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -1484,41 +1565,117 @@ func (s *APIV1Service) convertInboxToUserNotificationWithUsers(ctx context.Conte ...@@ -1484,41 +1565,117 @@ func (s *APIV1Service) convertInboxToUserNotificationWithUsers(ctx context.Conte
MemoComment: payload, MemoComment: payload,
} }
} }
case storepb.InboxMessage_MEMO_MENTION:
notification.Type = v1pb.UserNotification_MEMO_MENTION
payload, err := s.convertMemoMentionNotificationPayload(viewer, inbox.Message, memosByID)
if err != nil {
return nil, err
}
if payload != nil {
notification.Payload = &v1pb.UserNotification_MemoMention{
MemoMention: payload,
}
}
default:
notification.Type = v1pb.UserNotification_TYPE_UNSPECIFIED
}
} }
return notification, nil return notification, nil
} }
func (s *APIV1Service) convertUserNotificationPayload(ctx context.Context, message *storepb.InboxMessage) (*v1pb.UserNotification_MemoCommentPayload, error) { func canViewerAccessMemo(viewer *store.User, memo *store.Memo) bool {
if memo == nil {
return false
}
if viewer != nil && isSuperUser(viewer) {
return true
}
if memo.Visibility == store.Private {
return viewer != nil && viewer.ID == memo.CreatorID
}
if memo.Visibility == store.Protected {
return viewer != nil
}
return true
}
func (s *APIV1Service) memoNotificationSnippet(memo *store.Memo) (string, error) {
if memo == nil || memo.Content == "" {
return "", nil
}
snippet, err := s.getMemoContentSnippet(memo.Content)
if err != nil {
return "", err
}
return snippet, nil
}
func (s *APIV1Service) convertMemoCommentNotificationPayload(viewer *store.User, message *storepb.InboxMessage, memosByID map[int32]*store.Memo) (*v1pb.UserNotification_MemoCommentPayload, error) {
memoComment := message.GetMemoComment() memoComment := message.GetMemoComment()
if message == nil || message.Type != storepb.InboxMessage_MEMO_COMMENT || memoComment == nil { if message == nil || message.Type != storepb.InboxMessage_MEMO_COMMENT || memoComment == nil {
return nil, nil return nil, nil
} }
commentMemo, err := s.Store.GetMemo(ctx, &store.FindMemo{ commentMemo := memosByID[memoComment.MemoId]
ID: &memoComment.MemoId, if !canViewerAccessMemo(viewer, commentMemo) {
ExcludeContent: true, return nil, nil
})
if err != nil {
return nil, errors.Wrap(err, "failed to get comment memo")
} }
if commentMemo == nil {
relatedMemo := memosByID[memoComment.RelatedMemoId]
if !canViewerAccessMemo(viewer, relatedMemo) {
return nil, nil return nil, nil
} }
relatedMemo, err := s.Store.GetMemo(ctx, &store.FindMemo{ memoSnippet, err := s.memoNotificationSnippet(commentMemo)
ID: &memoComment.RelatedMemoId,
ExcludeContent: true,
})
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to get related memo") return nil, errors.Wrap(err, "failed to get comment memo snippet")
} }
if relatedMemo == nil { relatedMemoSnippet, err := s.memoNotificationSnippet(relatedMemo)
return nil, nil if err != nil {
return nil, errors.Wrap(err, "failed to get related memo snippet")
} }
return &v1pb.UserNotification_MemoCommentPayload{ return &v1pb.UserNotification_MemoCommentPayload{
Memo: fmt.Sprintf("%s%s", MemoNamePrefix, commentMemo.UID), Memo: fmt.Sprintf("%s%s", MemoNamePrefix, commentMemo.UID),
RelatedMemo: fmt.Sprintf("%s%s", MemoNamePrefix, relatedMemo.UID), RelatedMemo: fmt.Sprintf("%s%s", MemoNamePrefix, relatedMemo.UID),
MemoSnippet: memoSnippet,
RelatedMemoSnippet: relatedMemoSnippet,
}, nil }, nil
} }
func (s *APIV1Service) convertMemoMentionNotificationPayload(viewer *store.User, message *storepb.InboxMessage, memosByID map[int32]*store.Memo) (*v1pb.UserNotification_MemoMentionPayload, error) {
memoMention := message.GetMemoMention()
if message == nil || message.Type != storepb.InboxMessage_MEMO_MENTION || memoMention == nil {
return nil, nil
}
memo := memosByID[memoMention.MemoId]
if !canViewerAccessMemo(viewer, memo) {
return nil, nil
}
memoSnippet, err := s.memoNotificationSnippet(memo)
if err != nil {
return nil, errors.Wrap(err, "failed to get mention memo snippet")
}
payload := &v1pb.UserNotification_MemoMentionPayload{
Memo: fmt.Sprintf("%s%s", MemoNamePrefix, memo.UID),
MemoSnippet: memoSnippet,
}
if memoMention.RelatedMemoId != 0 {
relatedMemo := memosByID[memoMention.RelatedMemoId]
if canViewerAccessMemo(viewer, relatedMemo) {
payload.RelatedMemo = fmt.Sprintf("%s%s", MemoNamePrefix, relatedMemo.UID)
relatedMemoSnippet, err := s.memoNotificationSnippet(relatedMemo)
if err != nil {
return nil, errors.Wrap(err, "failed to get related memo snippet")
}
payload.RelatedMemoSnippet = relatedMemoSnippet
}
}
return payload, nil
}
...@@ -39,6 +39,7 @@ type APIV1Service struct { ...@@ -39,6 +39,7 @@ type APIV1Service struct {
func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store) *APIV1Service { func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store) *APIV1Service {
markdownService := markdown.NewService( markdownService := markdown.NewService(
markdown.WithTagExtension(), markdown.WithTagExtension(),
markdown.WithMentionExtension(),
) )
return &APIV1Service{ return &APIV1Service{
Secret: secret, Secret: secret,
......
...@@ -49,7 +49,7 @@ func (r *Runner) RunOnce(ctx context.Context) { ...@@ -49,7 +49,7 @@ func (r *Runner) RunOnce(ctx context.Context) {
// Process batch // Process batch
batchSuccessCount := 0 batchSuccessCount := 0
for _, memo := range memos { for _, memo := range memos {
if err := RebuildMemoPayload(memo, r.MarkdownService); err != nil { if err := RebuildMemoPayload(ctx, memo, r.MarkdownService); err != nil {
slog.Error("failed to rebuild memo payload", "err", err, "memoID", memo.ID) slog.Error("failed to rebuild memo payload", "err", err, "memoID", memo.ID)
continue continue
} }
...@@ -71,7 +71,7 @@ func (r *Runner) RunOnce(ctx context.Context) { ...@@ -71,7 +71,7 @@ func (r *Runner) RunOnce(ctx context.Context) {
} }
} }
func RebuildMemoPayload(memo *store.Memo, markdownService markdown.Service) error { func RebuildMemoPayload(_ context.Context, memo *store.Memo, markdownService markdown.Service) error {
if memo.Payload == nil { if memo.Payload == nil {
memo.Payload = &storepb.MemoPayload{} memo.Payload = &storepb.MemoPayload{}
} }
......
...@@ -83,6 +83,7 @@ func (d *DB) UpdateUser(ctx context.Context, update *store.UpdateUser) (*store.U ...@@ -83,6 +83,7 @@ func (d *DB) UpdateUser(ctx context.Context, update *store.UpdateUser) (*store.U
func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User, error) { func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User, error) {
where, args := []string{"1 = 1"}, []any{} where, args := []string{"1 = 1"}, []any{}
orderBy := []string{"`created_ts` DESC", "`row_status` DESC"}
if len(find.Filters) > 0 { if len(find.Filters) > 0 {
return nil, errors.Errorf("user filters are not supported") return nil, errors.Errorf("user filters are not supported")
...@@ -104,6 +105,22 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User ...@@ -104,6 +105,22 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User
return list return list
}()...) }()...)
} }
if len(find.UsernameList) > 0 {
placeholders := make([]string, 0, len(find.UsernameList))
for range find.UsernameList {
placeholders = append(placeholders, "?")
}
where, args = append(where, fmt.Sprintf("`username` IN (%s)", strings.Join(placeholders, ", "))), append(args, func() []any {
list := make([]any, 0, len(find.UsernameList))
for _, username := range find.UsernameList {
list = append(list, username)
}
return list
}()...)
}
if v := find.RowStatus; v != nil {
where, args = append(where, "`row_status` = ?"), append(args, *v)
}
if v := find.Username; v != nil { if v := find.Username; v != nil {
where, args = append(where, "`username` = ?"), append(args, *v) where, args = append(where, "`username` = ?"), append(args, *v)
} }
...@@ -116,8 +133,17 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User ...@@ -116,8 +133,17 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User
if v := find.Nickname; v != nil { if v := find.Nickname; v != nil {
where, args = append(where, "`nickname` = ?"), append(args, *v) where, args = append(where, "`nickname` = ?"), append(args, *v)
} }
if v := find.Search; v != nil && strings.TrimSpace(*v) != "" {
orderBy := []string{"`created_ts` DESC", "`row_status` DESC"} query := strings.ToLower(strings.TrimSpace(*v))
where, args = append(where, "(LOWER(`username`) LIKE ? OR LOWER(`nickname`) LIKE ?)"), append(args, "%"+query+"%", "%"+query+"%")
orderBy = []string{
"CASE WHEN LOWER(`username`) = ? THEN 0 WHEN LOWER(`username`) LIKE ? THEN 1 WHEN LOWER(`nickname`) LIKE ? THEN 2 ELSE 3 END",
"CHAR_LENGTH(`username`) ASC",
"`created_ts` DESC",
"`row_status` DESC",
}
args = append(args, query, query+"%", query+"%")
}
query := "SELECT `id`, `username`, `role`, `email`, `nickname`, `password_hash`, `avatar_url`, `description`, UNIX_TIMESTAMP(`created_ts`), UNIX_TIMESTAMP(`updated_ts`), `row_status` FROM `user` WHERE " + strings.Join(where, " AND ") + " ORDER BY " + strings.Join(orderBy, ", ") query := "SELECT `id`, `username`, `role`, `email`, `nickname`, `password_hash`, `avatar_url`, `description`, UNIX_TIMESTAMP(`created_ts`), UNIX_TIMESTAMP(`updated_ts`), `row_status` FROM `user` WHERE " + strings.Join(where, " AND ") + " ORDER BY " + strings.Join(orderBy, ", ")
if v := find.Limit; v != nil { if v := find.Limit; v != nil {
query += fmt.Sprintf(" LIMIT %d", *v) query += fmt.Sprintf(" LIMIT %d", *v)
......
...@@ -86,6 +86,7 @@ func (d *DB) UpdateUser(ctx context.Context, update *store.UpdateUser) (*store.U ...@@ -86,6 +86,7 @@ func (d *DB) UpdateUser(ctx context.Context, update *store.UpdateUser) (*store.U
func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User, error) { func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User, error) {
where, args := []string{"1 = 1"}, []any{} where, args := []string{"1 = 1"}, []any{}
orderBy := []string{"created_ts DESC", "row_status DESC"}
if len(find.Filters) > 0 { if len(find.Filters) > 0 {
return nil, errors.Errorf("user filters are not supported") return nil, errors.Errorf("user filters are not supported")
...@@ -102,6 +103,17 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User ...@@ -102,6 +103,17 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User
} }
where = append(where, fmt.Sprintf("id IN (%s)", strings.Join(holders, ", "))) where = append(where, fmt.Sprintf("id IN (%s)", strings.Join(holders, ", ")))
} }
if len(find.UsernameList) > 0 {
holders := make([]string, 0, len(find.UsernameList))
for _, username := range find.UsernameList {
holders = append(holders, placeholder(len(args)+1))
args = append(args, username)
}
where = append(where, fmt.Sprintf("username IN (%s)", strings.Join(holders, ", ")))
}
if v := find.RowStatus; v != nil {
where, args = append(where, "row_status = "+placeholder(len(args)+1)), append(args, *v)
}
if v := find.Username; v != nil { if v := find.Username; v != nil {
where, args = append(where, "username = "+placeholder(len(args)+1)), append(args, *v) where, args = append(where, "username = "+placeholder(len(args)+1)), append(args, *v)
} }
...@@ -114,8 +126,19 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User ...@@ -114,8 +126,19 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User
if v := find.Nickname; v != nil { if v := find.Nickname; v != nil {
where, args = append(where, "nickname = "+placeholder(len(args)+1)), append(args, *v) where, args = append(where, "nickname = "+placeholder(len(args)+1)), append(args, *v)
} }
if v := find.Search; v != nil && strings.TrimSpace(*v) != "" {
orderBy := []string{"created_ts DESC", "row_status DESC"} query := strings.ToLower(strings.TrimSpace(*v))
where, args = append(where, "(LOWER(username) LIKE "+placeholder(len(args)+1)+" OR LOWER(nickname) LIKE "+placeholder(len(args)+2)+")"), append(args, "%"+query+"%", "%"+query+"%")
orderBy = []string{
"CASE WHEN LOWER(username) = " + placeholder(len(args)+1) + " THEN 0 " +
"WHEN LOWER(username) LIKE " + placeholder(len(args)+2) + " THEN 1 " +
"WHEN LOWER(nickname) LIKE " + placeholder(len(args)+3) + " THEN 2 ELSE 3 END",
"LENGTH(username) ASC",
"created_ts DESC",
"row_status DESC",
}
args = append(args, query, query+"%", query+"%")
}
query := ` query := `
SELECT SELECT
id, id,
......
...@@ -87,6 +87,7 @@ func (d *DB) UpdateUser(ctx context.Context, update *store.UpdateUser) (*store.U ...@@ -87,6 +87,7 @@ func (d *DB) UpdateUser(ctx context.Context, update *store.UpdateUser) (*store.U
func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User, error) { func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User, error) {
where, args := []string{"1 = 1"}, []any{} where, args := []string{"1 = 1"}, []any{}
orderBy := []string{"created_ts DESC", "row_status DESC"}
if len(find.Filters) > 0 { if len(find.Filters) > 0 {
return nil, errors.Errorf("user filters are not supported") return nil, errors.Errorf("user filters are not supported")
...@@ -108,6 +109,22 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User ...@@ -108,6 +109,22 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User
return list return list
}()...) }()...)
} }
if len(find.UsernameList) > 0 {
placeholders := make([]string, 0, len(find.UsernameList))
for range find.UsernameList {
placeholders = append(placeholders, "?")
}
where, args = append(where, fmt.Sprintf("username IN (%s)", strings.Join(placeholders, ", "))), append(args, func() []any {
list := make([]any, 0, len(find.UsernameList))
for _, username := range find.UsernameList {
list = append(list, username)
}
return list
}()...)
}
if v := find.RowStatus; v != nil {
where, args = append(where, "row_status = ?"), append(args, *v)
}
if v := find.Username; v != nil { if v := find.Username; v != nil {
where, args = append(where, "username = ?"), append(args, *v) where, args = append(where, "username = ?"), append(args, *v)
} }
...@@ -120,8 +137,17 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User ...@@ -120,8 +137,17 @@ func (d *DB) ListUsers(ctx context.Context, find *store.FindUser) ([]*store.User
if v := find.Nickname; v != nil { if v := find.Nickname; v != nil {
where, args = append(where, "nickname = ?"), append(args, *v) where, args = append(where, "nickname = ?"), append(args, *v)
} }
if v := find.Search; v != nil && strings.TrimSpace(*v) != "" {
orderBy := []string{"created_ts DESC", "row_status DESC"} query := strings.ToLower(strings.TrimSpace(*v))
where, args = append(where, "(LOWER(username) LIKE ? OR LOWER(nickname) LIKE ?)"), append(args, "%"+query+"%", "%"+query+"%")
orderBy = []string{
"CASE WHEN LOWER(username) = ? THEN 0 WHEN LOWER(username) LIKE ? THEN 1 WHEN LOWER(nickname) LIKE ? THEN 2 ELSE 3 END",
"LENGTH(username) ASC",
"created_ts DESC",
"row_status DESC",
}
args = append(args, query, query+"%", query+"%")
}
query := ` query := `
SELECT SELECT
id, id,
......
...@@ -60,11 +60,14 @@ type FindUser struct { ...@@ -60,11 +60,14 @@ type FindUser struct {
ID *int32 ID *int32
IDList []int32 IDList []int32
UsernameList []string
RowStatus *RowStatus RowStatus *RowStatus
Username *string Username *string
Role *Role Role *Role
Email *string Email *string
Nickname *string Nickname *string
Search *string
// Domain specific fields // Domain specific fields
Filters []string Filters []string
......
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { FieldMaskSchema, timestampDate } from "@bufbuild/protobuf/wkt"; import { FieldMaskSchema, timestampDate } from "@bufbuild/protobuf/wkt";
import { CheckIcon, MessageCircleIcon, TrashIcon, XIcon } from "lucide-react"; import { CheckIcon, MessageCircleIcon, TrashIcon, XIcon } from "lucide-react";
import { useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import UserAvatar from "@/components/UserAvatar"; import UserAvatar from "@/components/UserAvatar";
import { memoServiceClient, userServiceClient } from "@/connect"; import { userServiceClient } from "@/connect";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { useUser } from "@/hooks/useUserQueries";
import { handleError } from "@/lib/error";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Memo } from "@/types/proto/api/v1/memo_service_pb";
import { UserNotification, UserNotification_Status } from "@/types/proto/api/v1/user_service_pb"; import { UserNotification, UserNotification_Status } from "@/types/proto/api/v1/user_service_pb";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
...@@ -21,53 +16,8 @@ interface Props { ...@@ -21,53 +16,8 @@ interface Props {
function MemoCommentMessage({ notification }: Props) { function MemoCommentMessage({ notification }: Props) {
const t = useTranslate(); const t = useTranslate();
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const [relatedMemo, setRelatedMemo] = useState<Memo | undefined>(undefined); const commentPayload = notification.payload?.case === "memoComment" ? notification.payload.value : undefined;
const [commentMemo, setCommentMemo] = useState<Memo | undefined>(undefined); const sender = notification.senderUser;
const [senderName, setSenderName] = useState<string | undefined>(undefined);
const [initialized, setInitialized] = useState<boolean>(false);
const [hasError, setHasError] = useState<boolean>(false);
const { data: sender } = useUser(senderName || "", { enabled: !!senderName });
useAsyncEffect(async () => {
if (notification.payload?.case !== "memoComment") {
setHasError(true);
return;
}
try {
const memoCommentPayload = notification.payload.value;
const memo = await memoServiceClient.getMemo({
name: memoCommentPayload.relatedMemo,
});
setRelatedMemo(memo);
const comment = await memoServiceClient.getMemo({
name: memoCommentPayload.memo,
});
setCommentMemo(comment);
setSenderName(notification.sender);
setInitialized(true);
} catch (error) {
handleError(error, () => {}, {
context: "Failed to fetch memo comment notification",
onError: () => setHasError(true),
});
return;
}
}, [notification.payload, notification.sender]);
const handleNavigateToMemo = async () => {
if (!relatedMemo) {
return;
}
navigateTo(`/${relatedMemo.name}`);
if (notification.status === UserNotification_Status.UNREAD) {
handleArchiveMessage(true);
}
};
const handleArchiveMessage = async (silence = false) => { const handleArchiveMessage = async (silence = false) => {
await userServiceClient.updateUserNotification({ await userServiceClient.updateUserNotification({
...@@ -89,22 +39,7 @@ function MemoCommentMessage({ notification }: Props) { ...@@ -89,22 +39,7 @@ function MemoCommentMessage({ notification }: Props) {
toast.success(t("message.deleted-successfully")); toast.success(t("message.deleted-successfully"));
}; };
if (!initialized && !hasError) { if (!commentPayload) {
return (
<div className="w-full px-5 py-4 border-b border-border/60 last:border-b-0 bg-muted/10 animate-pulse">
<div className="flex items-start gap-3">
<div className="w-10 h-10 rounded-full bg-muted/50 shrink-0" />
<div className="flex-1 space-y-3">
<div className="h-4 bg-muted/50 rounded-md w-2/5" />
<div className="h-3 bg-muted/40 rounded-md w-3/4" />
<div className="h-20 bg-muted/30 rounded-xl" />
</div>
</div>
</div>
);
}
if (hasError) {
return ( return (
<div className="w-full px-5 py-4 border-b border-border/60 last:border-b-0 bg-destructive/[0.04] group"> <div className="w-full px-5 py-4 border-b border-border/60 last:border-b-0 bg-destructive/[0.04] group">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
...@@ -128,6 +63,13 @@ function MemoCommentMessage({ notification }: Props) { ...@@ -128,6 +63,13 @@ function MemoCommentMessage({ notification }: Props) {
const isUnread = notification.status === UserNotification_Status.UNREAD; const isUnread = notification.status === UserNotification_Status.UNREAD;
const handleNavigateToMemo = async () => {
navigateTo(`/${commentPayload.relatedMemo}`);
if (isUnread) {
await handleArchiveMessage(true);
}
};
return ( return (
<div <div
className={cn( className={cn(
...@@ -135,11 +77,9 @@ function MemoCommentMessage({ notification }: Props) { ...@@ -135,11 +77,9 @@ function MemoCommentMessage({ notification }: Props) {
isUnread ? "bg-primary/[0.03] hover:bg-primary/[0.05]" : "hover:bg-muted/30", isUnread ? "bg-primary/[0.03] hover:bg-primary/[0.05]" : "hover:bg-muted/30",
)} )}
> >
{/* Unread indicator bar */}
{isUnread && <div className="absolute left-0 top-0 bottom-0 w-0.5 bg-gradient-to-b from-primary to-primary/60" />} {isUnread && <div className="absolute left-0 top-0 bottom-0 w-0.5 bg-gradient-to-b from-primary to-primary/60" />}
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
{/* Avatar & Icon */}
<div className="relative shrink-0"> <div className="relative shrink-0">
<UserAvatar className="w-10 h-10 ring-1 ring-border/40" avatarUrl={sender?.avatarUrl} /> <UserAvatar className="w-10 h-10 ring-1 ring-border/40" avatarUrl={sender?.avatarUrl} />
<div <div
...@@ -152,9 +92,7 @@ function MemoCommentMessage({ notification }: Props) { ...@@ -152,9 +92,7 @@ function MemoCommentMessage({ notification }: Props) {
</div> </div>
</div> </div>
{/* Content */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{/* Header */}
<div className="flex items-center justify-between gap-3 mb-1"> <div className="flex items-center justify-between gap-3 mb-1">
<div className="flex items-center gap-1.5 flex-wrap min-w-0"> <div className="flex items-center gap-1.5 flex-wrap min-w-0">
<span className="font-semibold text-sm text-foreground/95">{sender?.displayName || sender?.username}</span> <span className="font-semibold text-sm text-foreground/95">{sender?.displayName || sender?.username}</span>
...@@ -188,18 +126,13 @@ function MemoCommentMessage({ notification }: Props) { ...@@ -188,18 +126,13 @@ function MemoCommentMessage({ notification }: Props) {
</div> </div>
</div> </div>
{/* Original Memo Snippet */}
{relatedMemo && (
<div className="pl-3 border-l-2 border-muted-foreground/20 mb-3"> <div className="pl-3 border-l-2 border-muted-foreground/20 mb-3">
<p className="text-sm text-foreground/60 line-clamp-1 leading-relaxed"> <p className="text-sm text-foreground/60 line-clamp-1 leading-relaxed">
<span className="text-xs text-muted-foreground/50 font-medium mr-2 uppercase tracking-wide">Original:</span> <span className="text-xs text-muted-foreground/50 font-medium mr-2 uppercase tracking-wide">Original:</span>
{relatedMemo.content || <span className="italic text-muted-foreground/40">Empty memo</span>} {commentPayload.relatedMemoSnippet || <span className="italic text-muted-foreground/40">Empty memo</span>}
</p> </p>
</div> </div>
)}
{/* Comment Preview */}
{commentMemo && (
<div <div
onClick={handleNavigateToMemo} onClick={handleNavigateToMemo}
className="p-2 sm:p-3 rounded-lg bg-gradient-to-br from-primary/[0.06] to-primary/[0.03] hover:from-primary/[0.1] hover:to-primary/[0.06] cursor-pointer border border-primary/30 hover:border-primary/50 transition-all duration-200 group/comment shadow-sm hover:shadow" className="p-2 sm:p-3 rounded-lg bg-gradient-to-br from-primary/[0.06] to-primary/[0.03] hover:from-primary/[0.1] hover:to-primary/[0.06] cursor-pointer border border-primary/30 hover:border-primary/50 transition-all duration-200 group/comment shadow-sm hover:shadow"
...@@ -211,12 +144,11 @@ function MemoCommentMessage({ notification }: Props) { ...@@ -211,12 +144,11 @@ function MemoCommentMessage({ notification }: Props) {
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-xs text-primary/60 font-semibold mb-1 uppercase tracking-wider">Comment</p> <p className="text-xs text-primary/60 font-semibold mb-1 uppercase tracking-wider">Comment</p>
<p className="text-sm text-foreground/90 line-clamp-2"> <p className="text-sm text-foreground/90 line-clamp-2">
{commentMemo.content || <span className="italic text-muted-foreground/50">Empty comment</span>} {commentPayload.memoSnippet || <span className="italic text-muted-foreground/50">Empty comment</span>}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
)}
</div> </div>
</div> </div>
</div> </div>
......
import { create } from "@bufbuild/protobuf";
import { FieldMaskSchema, timestampDate } from "@bufbuild/protobuf/wkt";
import { AtSignIcon, CheckIcon, MessageSquareIcon, TrashIcon, XIcon } from "lucide-react";
import toast from "react-hot-toast";
import UserAvatar from "@/components/UserAvatar";
import { userServiceClient } from "@/connect";
import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { UserNotification, UserNotification_Status } from "@/types/proto/api/v1/user_service_pb";
import { useTranslate } from "@/utils/i18n";
interface Props {
notification: UserNotification;
}
function MemoMentionMessage({ notification }: Props) {
const t = useTranslate();
const navigateTo = useNavigateTo();
const mentionPayload = notification.payload?.case === "memoMention" ? notification.payload.value : undefined;
const sender = notification.senderUser;
const handleArchiveMessage = async (silence = false) => {
await userServiceClient.updateUserNotification({
notification: {
name: notification.name,
status: UserNotification_Status.ARCHIVED,
},
updateMask: create(FieldMaskSchema, { paths: ["status"] }),
});
if (!silence) {
toast.success(t("message.archived-successfully"));
}
};
const handleDeleteMessage = async () => {
await userServiceClient.deleteUserNotification({
name: notification.name,
});
toast.success(t("message.deleted-successfully"));
};
if (!mentionPayload) {
return (
<div className="w-full px-5 py-4 border-b border-border/60 last:border-b-0 bg-destructive/[0.04] group">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-destructive/15 flex items-center justify-center shrink-0 ring-1 ring-destructive/20">
<XIcon className="w-5 h-5 text-destructive" strokeWidth={2} />
</div>
<span className="text-sm text-destructive/80 font-medium">{t("inbox.failed-to-load")}</span>
</div>
<button
onClick={handleDeleteMessage}
className="p-1.5 hover:bg-destructive/15 rounded-lg transition-all duration-150 opacity-0 group-hover:opacity-100"
title={t("common.delete")}
>
<TrashIcon className="w-4 h-4 text-destructive/70 hover:text-destructive transition-colors" strokeWidth={2} />
</button>
</div>
</div>
);
}
const isUnread = notification.status === UserNotification_Status.UNREAD;
const isCommentMention = Boolean(mentionPayload.relatedMemo);
const targetName = mentionPayload.relatedMemo || mentionPayload.memo;
const handleNavigate = async () => {
navigateTo(`/${targetName}`);
if (isUnread) {
await handleArchiveMessage(true);
}
};
return (
<div
className={cn(
"w-full px-5 py-4 border-b border-border/60 last:border-b-0 transition-all duration-200 group relative",
isUnread ? "bg-primary/[0.03] hover:bg-primary/[0.05]" : "hover:bg-muted/30",
)}
>
{isUnread && <div className="absolute left-0 top-0 bottom-0 w-0.5 bg-gradient-to-b from-primary to-primary/60" />}
<div className="flex items-start gap-3">
<div className="relative shrink-0">
<UserAvatar className="w-10 h-10 ring-1 ring-border/40" avatarUrl={sender?.avatarUrl} />
<div
className={cn(
"absolute -bottom-1 -right-1 w-5 h-5 rounded-full border-2 border-background flex items-center justify-center shadow-md transition-all",
isUnread ? "bg-primary text-primary-foreground" : "bg-muted/80 text-muted-foreground",
)}
>
<AtSignIcon className="w-2.5 h-2.5" strokeWidth={2.5} />
</div>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-3 mb-1">
<div className="flex items-center gap-1.5 flex-wrap min-w-0">
<span className="font-semibold text-sm text-foreground/95">{sender?.displayName || sender?.username}</span>
<span className="text-sm text-muted-foreground/80">mentioned you {isCommentMention ? "in a comment" : "in a memo"}</span>
<span className="text-xs text-muted-foreground/60">
{notification.createTime &&
timestampDate(notification.createTime)?.toLocaleDateString([], { month: "short", day: "numeric" })}{" "}
at{" "}
{notification.createTime &&
timestampDate(notification.createTime)?.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
</span>
</div>
<div className="flex items-center gap-1 shrink-0">
{isUnread ? (
<button
onClick={() => handleArchiveMessage()}
className="p-1.5 hover:bg-primary/10 rounded-lg transition-all duration-150 opacity-0 group-hover:opacity-100"
title={t("common.archive")}
>
<CheckIcon className="w-4 h-4 text-muted-foreground hover:text-primary transition-colors" strokeWidth={2} />
</button>
) : (
<button
onClick={handleDeleteMessage}
className="p-1.5 hover:bg-destructive/10 rounded-lg transition-all duration-150 opacity-0 group-hover:opacity-100"
title={t("common.delete")}
>
<TrashIcon className="w-4 h-4 text-muted-foreground hover:text-destructive transition-colors" strokeWidth={2} />
</button>
)}
</div>
</div>
{mentionPayload.relatedMemo && (
<div className="pl-3 border-l-2 border-muted-foreground/20 mb-3">
<p className="text-sm text-foreground/60 line-clamp-1 leading-relaxed">
<span className="text-xs text-muted-foreground/50 font-medium mr-2 uppercase tracking-wide">Memo:</span>
{mentionPayload.relatedMemoSnippet || <span className="italic text-muted-foreground/40">Empty memo</span>}
</p>
</div>
)}
<div
onClick={handleNavigate}
className="p-2 sm:p-3 rounded-lg bg-gradient-to-br from-primary/[0.06] to-primary/[0.03] hover:from-primary/[0.1] hover:to-primary/[0.06] cursor-pointer border border-primary/30 hover:border-primary/50 transition-all duration-200 group/comment shadow-sm hover:shadow"
>
<div className="flex items-start gap-2">
<div className="w-5 h-5 flex items-center justify-center shrink-0">
<MessageSquareIcon className="w-4 h-4 text-primary" />
</div>
<div className="flex-1 min-w-0">
<p className="text-xs text-primary/60 font-semibold mb-1 uppercase tracking-wider">
{isCommentMention ? "Comment" : "Memo"}
</p>
<p className="text-sm text-foreground/90 line-clamp-2">
{mentionPayload.memoSnippet || <span className="italic text-muted-foreground/50">Empty memo</span>}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default MemoMentionMessage;
import type { Element } from "hast"; import type { Element } from "hast";
import React from "react"; import React from "react";
import { isTagElement, isTaskListItemElement } from "@/types/markdown"; import { isMentionElement, isTagElement, isTaskListItemElement } from "@/types/markdown";
/** /**
* Creates a conditional component that renders different components * Creates a conditional component that renders different components
...@@ -33,4 +33,4 @@ export const createConditionalComponent = <P extends Record<string, unknown>>( ...@@ -33,4 +33,4 @@ export const createConditionalComponent = <P extends Record<string, unknown>>(
}; };
// Re-export type guards for convenience // Re-export type guards for convenience
export { isTagElement as isTagNode, isTaskListItemElement as isTaskListItemNode }; export { isMentionElement as isMentionNode, isTagElement as isTagNode, isTaskListItemElement as isTaskListItemNode };
import type { Element } from "hast";
import { cn } from "@/lib/utils";
interface MentionProps extends React.HTMLAttributes<HTMLSpanElement> {
node?: Element;
"data-mention"?: string;
children?: React.ReactNode;
resolved?: boolean;
}
export const Mention: React.FC<MentionProps> = ({
"data-mention": dataMention,
children,
className,
node: _node,
resolved = false,
...props
}) => {
const username = dataMention || "";
if (!resolved) {
return (
<span data-mention={username} title={`@${username}`} className={className} {...props}>
{children}
</span>
);
}
return (
<a
href={`/u/${username}`}
className={cn("text-blue-600 underline-offset-2 hover:underline dark:text-blue-400", className)}
data-mention={username}
title={`@${username}`}
{...props}
>
{children}
</a>
);
};
import { createContext, type ReactNode, useContext, useMemo } from "react";
import { useUsersByUsernames } from "@/hooks/useUserQueries";
import { extractMentionUsernames } from "@/utils/remark-plugins/remark-mention";
const MentionResolutionContext = createContext<Set<string> | null>(null);
interface MentionResolutionProviderProps {
contents: string[];
children: ReactNode;
}
export const MentionResolutionProvider = ({ contents, children }: MentionResolutionProviderProps) => {
const mentionUsernames = useMemo(() => Array.from(new Set(contents.flatMap((content) => extractMentionUsernames(content)))), [contents]);
const { data: mentionUsers } = useUsersByUsernames(mentionUsernames);
const resolvedMentionUsernames = useMemo(() => {
if (!mentionUsers) {
return new Set<string>();
}
return new Set(Array.from(mentionUsers.entries()).flatMap(([username, user]) => (user ? [username] : [])));
}, [mentionUsers]);
return <MentionResolutionContext.Provider value={resolvedMentionUsernames}>{children}</MentionResolutionContext.Provider>;
};
export function useResolvedMentionUsernames(usernames: string[]) {
const sharedResolvedMentionUsernames = useContext(MentionResolutionContext);
const shouldUseSharedResolution = sharedResolvedMentionUsernames !== null;
const { data: mentionUsers } = useUsersByUsernames(usernames, { enabled: !shouldUseSharedResolution });
return useMemo(() => {
if (sharedResolvedMentionUsernames) {
return sharedResolvedMentionUsernames;
}
if (!mentionUsers) {
return new Set<string>();
}
return new Set(Array.from(mentionUsers.entries()).flatMap(([username, user]) => (user ? [username] : [])));
}, [sharedResolvedMentionUsernames, mentionUsers]);
}
import type { Element } from "hast"; import type { Element } from "hast";
import { ChevronDown, ChevronUp } from "lucide-react"; import { ChevronDown, ChevronUp } from "lucide-react";
import { memo } from "react"; import { memo, useMemo } from "react";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import rehypeKatex from "rehype-katex"; import rehypeKatex from "rehype-katex";
import rehypeRaw from "rehype-raw"; import rehypeRaw from "rehype-raw";
...@@ -12,18 +12,40 @@ import { cn } from "@/lib/utils"; ...@@ -12,18 +12,40 @@ import { cn } from "@/lib/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { rehypeHeadingId } from "@/utils/rehype-plugins/rehype-heading-id"; import { rehypeHeadingId } from "@/utils/rehype-plugins/rehype-heading-id";
import { remarkDisableSetext } from "@/utils/remark-plugins/remark-disable-setext"; import { remarkDisableSetext } from "@/utils/remark-plugins/remark-disable-setext";
import { extractMentionUsernames, remarkMention } from "@/utils/remark-plugins/remark-mention";
import { remarkPreserveType } from "@/utils/remark-plugins/remark-preserve-type"; import { remarkPreserveType } from "@/utils/remark-plugins/remark-preserve-type";
import { remarkTag } from "@/utils/remark-plugins/remark-tag"; import { remarkTag } from "@/utils/remark-plugins/remark-tag";
import { CodeBlock } from "./CodeBlock"; import { CodeBlock } from "./CodeBlock";
import { isTagNode, isTaskListItemNode } from "./ConditionalComponent"; import { isMentionNode, isTagNode, isTaskListItemNode } from "./ConditionalComponent";
import { COMPACT_MODE_CONFIG, SANITIZE_SCHEMA } from "./constants"; import { COMPACT_MODE_CONFIG, SANITIZE_SCHEMA } from "./constants";
import { useCompactLabel, useCompactMode } from "./hooks"; import { useCompactLabel, useCompactMode } from "./hooks";
import { Mention } from "./Mention";
import { useResolvedMentionUsernames } from "./MentionResolutionContext";
import { Blockquote, Heading, HorizontalRule, Image, InlineCode, Link, List, ListItem, Paragraph } from "./markdown"; import { Blockquote, Heading, HorizontalRule, Image, InlineCode, Link, List, ListItem, Paragraph } from "./markdown";
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "./Table"; import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "./Table";
import { Tag } from "./Tag"; import { Tag } from "./Tag";
import { TaskListItem } from "./TaskListItem"; import { TaskListItem } from "./TaskListItem";
import type { MemoContentProps } from "./types"; import type { MemoContentProps } from "./types";
function getMentionUsername(node: Element, children?: React.ReactNode): string {
const dataMention = node.properties?.["data-mention"];
if (typeof dataMention === "string" && dataMention !== "") {
return dataMention;
}
const camelDataMention = (node.properties as Record<string, unknown> | undefined)?.dataMention;
if (typeof camelDataMention === "string" && camelDataMention !== "") {
return camelDataMention;
}
const text = Array.isArray(children) ? children.join("") : children;
if (typeof text === "string" && text.startsWith("@")) {
return text.slice(1).toLowerCase();
}
return "";
}
const MemoContent = (props: MemoContentProps) => { const MemoContent = (props: MemoContentProps) => {
const { className, contentClassName, content, onClick, onDoubleClick } = props; const { className, contentClassName, content, onClick, onDoubleClick } = props;
const t = useTranslate(); const t = useTranslate();
...@@ -32,6 +54,8 @@ const MemoContent = (props: MemoContentProps) => { ...@@ -32,6 +54,8 @@ const MemoContent = (props: MemoContentProps) => {
mode: showCompactMode, mode: showCompactMode,
toggle: toggleCompactMode, toggle: toggleCompactMode,
} = useCompactMode(Boolean(props.compact)); } = useCompactMode(Boolean(props.compact));
const mentionUsernames = useMemo(() => extractMentionUsernames(content), [content]);
const resolvedMentionUsernames = useResolvedMentionUsernames(mentionUsernames);
const compactLabel = useCompactLabel(showCompactMode, t as (key: string) => string); const compactLabel = useCompactLabel(showCompactMode, t as (key: string) => string);
...@@ -51,7 +75,7 @@ const MemoContent = (props: MemoContentProps) => { ...@@ -51,7 +75,7 @@ const MemoContent = (props: MemoContentProps) => {
onDoubleClick={onDoubleClick} onDoubleClick={onDoubleClick}
> >
<ReactMarkdown <ReactMarkdown
remarkPlugins={[remarkDisableSetext, remarkMath, remarkGfm, remarkBreaks, remarkTag, remarkPreserveType]} remarkPlugins={[remarkDisableSetext, remarkMath, remarkGfm, remarkBreaks, remarkMention, remarkTag, remarkPreserveType]}
rehypePlugins={[ rehypePlugins={[
rehypeRaw, rehypeRaw,
[rehypeSanitize, SANITIZE_SCHEMA], [rehypeSanitize, SANITIZE_SCHEMA],
...@@ -69,6 +93,10 @@ const MemoContent = (props: MemoContentProps) => { ...@@ -69,6 +93,10 @@ const MemoContent = (props: MemoContentProps) => {
}) as React.ComponentType<React.ComponentProps<"input">>, }) as React.ComponentType<React.ComponentProps<"input">>,
span: ((spanProps: React.ComponentProps<"span"> & { node?: Element }) => { span: ((spanProps: React.ComponentProps<"span"> & { node?: Element }) => {
const { node, ...rest } = spanProps; const { node, ...rest } = spanProps;
if (node && isMentionNode(node)) {
const username = getMentionUsername(node, spanProps.children);
return <Mention {...spanProps} data-mention={username} resolved={resolvedMentionUsernames.has(username)} />;
}
if (node && isTagNode(node)) { if (node && isTagNode(node)) {
return <Tag {...spanProps} />; return <Tag {...spanProps} />;
} }
......
...@@ -22,6 +22,7 @@ export interface UseSuggestionsReturn<T> { ...@@ -22,6 +22,7 @@ export interface UseSuggestionsReturn<T> {
suggestions: T[]; suggestions: T[];
selectedIndex: number; selectedIndex: number;
isVisible: boolean; isVisible: boolean;
searchQuery: string;
handleItemSelect: (item: T) => void; handleItemSelect: (item: T) => void;
} }
...@@ -52,12 +53,17 @@ export function useSuggestions<T>({ ...@@ -52,12 +53,17 @@ export function useSuggestions<T>({
const hide = () => setPosition(null); const hide = () => setPosition(null);
const suggestionsRef = useRef<T[]>([]); const suggestionsRef = useRef<T[]>([]);
const searchQueryRef = useRef("");
suggestionsRef.current = (() => { suggestionsRef.current = (() => {
const [word] = getCurrentWord(); const [word] = getCurrentWord();
if (!word.startsWith(triggerChar)) return []; if (!word.startsWith(triggerChar)) return [];
const searchQuery = word.slice(triggerChar.length).toLowerCase(); searchQueryRef.current = word.slice(triggerChar.length).toLowerCase();
return filterItems(items, searchQuery); return filterItems(items, searchQueryRef.current);
})(); })();
if (suggestionsRef.current.length === 0) {
const [word] = getCurrentWord();
searchQueryRef.current = word.startsWith(triggerChar) ? word.slice(triggerChar.length).toLowerCase() : "";
}
const isVisibleRef = useRef(false); const isVisibleRef = useRef(false);
isVisibleRef.current = !!(position && suggestionsRef.current.length > 0); isVisibleRef.current = !!(position && suggestionsRef.current.length > 0);
...@@ -153,6 +159,7 @@ export function useSuggestions<T>({ ...@@ -153,6 +159,7 @@ export function useSuggestions<T>({
suggestions: suggestionsRef.current, suggestions: suggestionsRef.current,
selectedIndex, selectedIndex,
isVisible: isVisibleRef.current, isVisible: isVisibleRef.current,
searchQuery: searchQueryRef.current,
handleItemSelect: handleAutocomplete, handleItemSelect: handleAutocomplete,
}; };
} }
...@@ -2,6 +2,7 @@ import { useQueryClient } from "@tanstack/react-query"; ...@@ -2,6 +2,7 @@ import { useQueryClient } from "@tanstack/react-query";
import { ArrowUpIcon } from "lucide-react"; import { ArrowUpIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { matchPath } from "react-router-dom"; import { matchPath } from "react-router-dom";
import { MentionResolutionProvider } from "@/components/MemoContent/MentionResolutionContext";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { userServiceClient } from "@/connect"; import { userServiceClient } from "@/connect";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
...@@ -145,6 +146,7 @@ const PagedMemoList = (props: Props) => { ...@@ -145,6 +146,7 @@ const PagedMemoList = (props: Props) => {
}, [hasNextPage, isFetchingNextPage, fetchNextPage]); }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
const children = ( const children = (
<MentionResolutionProvider contents={sortedMemoList.map((memo) => memo.content)}>
<div className="flex flex-col justify-start w-full max-w-2xl mx-auto"> <div className="flex flex-col justify-start w-full max-w-2xl mx-auto">
{/* Show skeleton loader during initial load */} {/* Show skeleton loader during initial load */}
{isLoading ? ( {isLoading ? (
...@@ -176,6 +178,7 @@ const PagedMemoList = (props: Props) => { ...@@ -176,6 +178,7 @@ const PagedMemoList = (props: Props) => {
</> </>
)} )}
</div> </div>
</MentionResolutionProvider>
); );
return children; return children;
......
...@@ -6,6 +6,8 @@ import { buildUserSettingName } from "@/helpers/resource-names"; ...@@ -6,6 +6,8 @@ import { buildUserSettingName } from "@/helpers/resource-names";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { User, UserSetting, UserSetting_GeneralSetting, UserSetting_Key, UserSettingSchema } from "@/types/proto/api/v1/user_service_pb"; import { User, UserSetting, UserSetting_GeneralSetting, UserSetting_Key, UserSettingSchema } from "@/types/proto/api/v1/user_service_pb";
const BATCH_GET_USERS_LIMIT = 100;
// Query keys factory // Query keys factory
export const userKeys = { export const userKeys = {
all: ["users"] as const, all: ["users"] as const,
...@@ -16,7 +18,8 @@ export const userKeys = { ...@@ -16,7 +18,8 @@ export const userKeys = {
currentUser: () => [...userKeys.all, "current"] as const, currentUser: () => [...userKeys.all, "current"] as const,
shortcuts: () => [...userKeys.all, "shortcuts"] as const, shortcuts: () => [...userKeys.all, "shortcuts"] as const,
notifications: () => [...userKeys.all, "notifications"] as const, notifications: () => [...userKeys.all, "notifications"] as const,
byNames: (names: string[]) => [...userKeys.all, "byNames", ...names.sort()] as const, byNames: (names: string[]) => [...userKeys.all, "byNames", ...[...names].sort()] as const,
byUsernames: (usernames: string[]) => [...userKeys.all, "byUsernames", ...[...usernames].sort()] as const,
}; };
export function useUser(name: string, options?: { enabled?: boolean }) { export function useUser(name: string, options?: { enabled?: boolean }) {
...@@ -244,3 +247,30 @@ export function useUsersByNames(names: string[]) { ...@@ -244,3 +247,30 @@ export function useUsersByNames(names: string[]) {
staleTime: 1000 * 60 * 5, // 5 minutes - user profiles don't change often staleTime: 1000 * 60 * 5, // 5 minutes - user profiles don't change often
}); });
} }
// Hook to fetch multiple users by usernames (returns Map<username, User>)
export function useUsersByUsernames(usernames: string[], options?: { enabled?: boolean }) {
const enabled = (options?.enabled ?? true) && usernames.length > 0;
const uniqueUsernames = Array.from(new Set(usernames));
return useQuery({
queryKey: userKeys.byUsernames(uniqueUsernames),
queryFn: async () => {
const batches = [];
for (let i = 0; i < uniqueUsernames.length; i += BATCH_GET_USERS_LIMIT) {
batches.push(uniqueUsernames.slice(i, i + BATCH_GET_USERS_LIMIT));
}
const responses = await Promise.all(batches.map((batch) => userServiceClient.batchGetUsers({ usernames: batch })));
const usersByUsername = new Map(responses.flatMap((response) => response.users).map((user) => [user.username, user] as const));
const userMap = new Map<string, User | undefined>();
for (const username of uniqueUsernames) {
userMap.set(username, usersByUsername.get(username));
}
return userMap;
},
enabled,
staleTime: 1000 * 60 * 5,
});
}
...@@ -4,6 +4,7 @@ import { ArchiveIcon, BellIcon, InboxIcon } from "lucide-react"; ...@@ -4,6 +4,7 @@ import { ArchiveIcon, BellIcon, InboxIcon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import Empty from "@/components/Empty"; import Empty from "@/components/Empty";
import MemoCommentMessage from "@/components/Inbox/MemoCommentMessage"; import MemoCommentMessage from "@/components/Inbox/MemoCommentMessage";
import MemoMentionMessage from "@/components/Inbox/MemoMentionMessage";
import MobileHeader from "@/components/MobileHeader"; import MobileHeader from "@/components/MobileHeader";
import useMediaQuery from "@/hooks/useMediaQuery"; import useMediaQuery from "@/hooks/useMediaQuery";
import { useNotifications } from "@/hooks/useUserQueries"; import { useNotifications } from "@/hooks/useUserQueries";
...@@ -108,6 +109,9 @@ const Inboxes = () => { ...@@ -108,6 +109,9 @@ const Inboxes = () => {
if (notification.type === UserNotification_Type.MEMO_COMMENT) { if (notification.type === UserNotification_Type.MEMO_COMMENT) {
return <MemoCommentMessage key={notification.name} notification={notification} />; return <MemoCommentMessage key={notification.name} notification={notification} />;
} }
if (notification.type === UserNotification_Type.MEMO_MENTION) {
return <MemoMentionMessage key={notification.name} notification={notification} />;
}
return null; return null;
})} })}
</div> </div>
......
...@@ -3,6 +3,7 @@ import { ArrowUpLeftFromCircleIcon } from "lucide-react"; ...@@ -3,6 +3,7 @@ import { ArrowUpLeftFromCircleIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link, Navigate, useLocation, useParams } from "react-router-dom"; import { Link, Navigate, useLocation, useParams } from "react-router-dom";
import MemoCommentSection from "@/components/MemoCommentSection"; import MemoCommentSection from "@/components/MemoCommentSection";
import { MentionResolutionProvider } from "@/components/MemoContent/MentionResolutionContext";
import { MemoDetailSidebar, MemoDetailSidebarDrawer } from "@/components/MemoDetailSidebar"; import { MemoDetailSidebar, MemoDetailSidebarDrawer } from "@/components/MemoDetailSidebar";
import MemoView from "@/components/MemoView"; import MemoView from "@/components/MemoView";
import MobileHeader from "@/components/MobileHeader"; import MobileHeader from "@/components/MobileHeader";
...@@ -73,6 +74,7 @@ const MemoDetail = () => { ...@@ -73,6 +74,7 @@ const MemoDetail = () => {
const displayMemo = isShareMode const displayMemo = isShareMode
? { ...memo, attachments: withShareAttachmentLinks(memo.attachments as Attachment[], shareToken!) } ? { ...memo, attachments: withShareAttachmentLinks(memo.attachments as Attachment[], shareToken!) }
: memo; : memo;
const mentionResolutionContents = [displayMemo.content, ...comments.map((comment) => comment.content)];
return ( return (
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8"> <section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
...@@ -81,6 +83,7 @@ const MemoDetail = () => { ...@@ -81,6 +83,7 @@ const MemoDetail = () => {
<MemoDetailSidebarDrawer memo={displayMemo} onShareImageOpen={() => setShareImageDialogOpen(true)} /> <MemoDetailSidebarDrawer memo={displayMemo} onShareImageOpen={() => setShareImageDialogOpen(true)} />
</MobileHeader> </MobileHeader>
)} )}
<MentionResolutionProvider contents={mentionResolutionContents}>
<div className={cn("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}> <div className={cn("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
<div className={cn("w-full md:w-[calc(100%-15rem)]")}> <div className={cn("w-full md:w-[calc(100%-15rem)]")}>
{parentMemo && ( {parentMemo && (
...@@ -115,6 +118,7 @@ const MemoDetail = () => { ...@@ -115,6 +118,7 @@ const MemoDetail = () => {
</div> </div>
)} )}
</div> </div>
</MentionResolutionProvider>
</section> </section>
); );
}; };
......
...@@ -6,17 +6,34 @@ export interface TagNode { ...@@ -6,17 +6,34 @@ export interface TagNode {
data: TagNodeData; data: TagNodeData;
} }
export interface MentionNode {
type: "mentionNode";
value: string;
data: MentionNodeData;
}
export interface TagNodeData { export interface TagNodeData {
hName: "span"; hName: "span";
hProperties: TagNodeProperties; hProperties: TagNodeProperties;
hChildren: Array<{ type: "text"; value: string }>; hChildren: Array<{ type: "text"; value: string }>;
} }
export interface MentionNodeData {
hName: "span";
hProperties: MentionNodeProperties;
hChildren: Array<{ type: "text"; value: string }>;
}
export interface TagNodeProperties { export interface TagNodeProperties {
className: string; className: string;
"data-tag": string; "data-tag": string;
} }
export interface MentionNodeProperties {
className: string;
"data-mention": string;
}
export interface ExtendedData extends Data { export interface ExtendedData extends Data {
mdastType?: string; mdastType?: string;
} }
...@@ -30,10 +47,39 @@ export function isTagElement(node: HastElement): boolean { ...@@ -30,10 +47,39 @@ export function isTagElement(node: HastElement): boolean {
return true; return true;
} }
const dataTag = node.properties?.["data-tag"];
if (typeof dataTag === "string" && dataTag !== "") {
return true;
}
const className = node.properties?.className; const className = node.properties?.className;
if (Array.isArray(className) && className.includes("tag")) { if (Array.isArray(className) && className.includes("tag")) {
return true; return true;
} }
if (typeof className === "string" && className.split(/\s+/).includes("tag")) {
return true;
}
return false;
}
export function isMentionElement(node: HastElement): boolean {
if (hasExtendedData(node) && node.data.mdastType === "mentionNode") {
return true;
}
const dataMention = node.properties?.["data-mention"];
if (typeof dataMention === "string" && dataMention !== "") {
return true;
}
const className = node.properties?.className;
if (Array.isArray(className) && className.includes("mention")) {
return true;
}
if (typeof className === "string" && className.split(/\s+/).includes("mention")) {
return true;
}
return false; return false;
} }
......
...@@ -18,7 +18,7 @@ import type { Message } from "@bufbuild/protobuf"; ...@@ -18,7 +18,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file api/v1/user_service.proto. * Describes the file api/v1/user_service.proto.
*/ */
export const file_api_v1_user_service: GenFile = /*@__PURE__*/ export const file_api_v1_user_service: GenFile = /*@__PURE__*/
fileDesc("ChlhcGkvdjEvdXNlcl9zZXJ2aWNlLnByb3RvEgxtZW1vcy5hcGkudjEi1gMKBFVzZXISEQoEbmFtZRgBIAEoCUID4EEIEioKBHJvbGUYAiABKA4yFy5tZW1vcy5hcGkudjEuVXNlci5Sb2xlQgPgQQISFQoIdXNlcm5hbWUYAyABKAlCA+BBAhISCgVlbWFpbBgEIAEoCUID4EEBEhkKDGRpc3BsYXlfbmFtZRgFIAEoCUID4EEBEhcKCmF2YXRhcl91cmwYBiABKAlCA+BBARIYCgtkZXNjcmlwdGlvbhgHIAEoCUID4EEBEhUKCHBhc3N3b3JkGAggASgJQgPgQQQSJwoFc3RhdGUYCSABKA4yEy5tZW1vcy5hcGkudjEuU3RhdGVCA+BBAhI0CgtjcmVhdGVfdGltZRgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAxI0Cgt1cGRhdGVfdGltZRgLIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAyIxCgRSb2xlEhQKEFJPTEVfVU5TUEVDSUZJRUQQABIJCgVBRE1JThACEggKBFVTRVIQAzo36kE0ChFtZW1vcy5hcGkudjEvVXNlchIMdXNlcnMve3VzZXJ9GgRuYW1lKgV1c2VyczIEdXNlciJzChBMaXN0VXNlcnNSZXF1ZXN0EhYKCXBhZ2Vfc2l6ZRgBIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAiABKAlCA+BBARITCgZmaWx0ZXIYAyABKAlCA+BBARIZCgxzaG93X2RlbGV0ZWQYBCABKAhCA+BBASJjChFMaXN0VXNlcnNSZXNwb25zZRIhCgV1c2VycxgBIAMoCzISLm1lbW9zLmFwaS52MS5Vc2VyEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRISCgp0b3RhbF9zaXplGAMgASgFIm0KDkdldFVzZXJSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISMgoJcmVhZF9tYXNrGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLkZpZWxkTWFza0ID4EEBIogBChFDcmVhdGVVc2VyUmVxdWVzdBIoCgR1c2VyGAEgASgLMhIubWVtb3MuYXBpLnYxLlVzZXJCBuBBAuBBBBIUCgd1c2VyX2lkGAIgASgJQgPgQQESGgoNdmFsaWRhdGVfb25seRgDIAEoCEID4EEBEhcKCnJlcXVlc3RfaWQYBCABKAlCA+BBASKMAQoRVXBkYXRlVXNlclJlcXVlc3QSJQoEdXNlchgBIAEoCzISLm1lbW9zLmFwaS52MS5Vc2VyQgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQISGgoNYWxsb3dfbWlzc2luZxgDIAEoCEID4EEBIlAKEURlbGV0ZVVzZXJSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISEgoFZm9yY2UYAiABKAhCA+BBASLYAwoJVXNlclN0YXRzEhEKBG5hbWUYASABKAlCA+BBCBI7ChdtZW1vX2Rpc3BsYXlfdGltZXN0YW1wcxgCIAMoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASPgoPbWVtb190eXBlX3N0YXRzGAMgASgLMiUubWVtb3MuYXBpLnYxLlVzZXJTdGF0cy5NZW1vVHlwZVN0YXRzEjgKCXRhZ19jb3VudBgEIAMoCzIlLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMuVGFnQ291bnRFbnRyeRIUCgxwaW5uZWRfbWVtb3MYBSADKAkSGAoQdG90YWxfbWVtb19jb3VudBgGIAEoBRovCg1UYWdDb3VudEVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBToCOAEaXwoNTWVtb1R5cGVTdGF0cxISCgpsaW5rX2NvdW50GAEgASgFEhIKCmNvZGVfY291bnQYAiABKAUSEgoKdG9kb19jb3VudBgDIAEoBRISCgp1bmRvX2NvdW50GAQgASgFOj/qQTwKFm1lbW9zLmFwaS52MS9Vc2VyU3RhdHMSDHVzZXJzL3t1c2VyfSoJdXNlclN0YXRzMgl1c2VyU3RhdHMiPgoTR2V0VXNlclN0YXRzUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9Vc2VyIhkKF0xpc3RBbGxVc2VyU3RhdHNSZXF1ZXN0IkIKGExpc3RBbGxVc2VyU3RhdHNSZXNwb25zZRImCgVzdGF0cxgBIAMoCzIXLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMi5AMKC1VzZXJTZXR0aW5nEhEKBG5hbWUYASABKAlCA+BBCBJDCg9nZW5lcmFsX3NldHRpbmcYAiABKAsyKC5tZW1vcy5hcGkudjEuVXNlclNldHRpbmcuR2VuZXJhbFNldHRpbmdIABJFChB3ZWJob29rc19zZXR0aW5nGAUgASgLMikubWVtb3MuYXBpLnYxLlVzZXJTZXR0aW5nLldlYmhvb2tzU2V0dGluZ0gAGlcKDkdlbmVyYWxTZXR0aW5nEhMKBmxvY2FsZRgBIAEoCUID4EEBEhwKD21lbW9fdmlzaWJpbGl0eRgDIAEoCUID4EEBEhIKBXRoZW1lGAQgASgJQgPgQQEaPgoPV2ViaG9va3NTZXR0aW5nEisKCHdlYmhvb2tzGAEgAygLMhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rIjUKA0tleRITCg9LRVlfVU5TUEVDSUZJRUQQABILCgdHRU5FUkFMEAESDAoIV0VCSE9PS1MQBDpd6kFaChhtZW1vcy5hcGkudjEvVXNlclNldHRpbmcSI3VzZXJzL3t1c2VybmFtZX0vc2V0dGluZ3Mve3NldHRpbmd9Kgx1c2VyU2V0dGluZ3MyC3VzZXJTZXR0aW5nQgcKBXZhbHVlIkcKFUdldFVzZXJTZXR0aW5nUmVxdWVzdBIuCgRuYW1lGAEgASgJQiDgQQL6QRoKGG1lbW9zLmFwaS52MS9Vc2VyU2V0dGluZyKBAQoYVXBkYXRlVXNlclNldHRpbmdSZXF1ZXN0Ei8KB3NldHRpbmcYASABKAsyGS5tZW1vcy5hcGkudjEuVXNlclNldHRpbmdCA+BBAhI0Cgt1cGRhdGVfbWFzaxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE1hc2tCA+BBAiJ1ChdMaXN0VXNlclNldHRpbmdzUmVxdWVzdBIpCgZwYXJlbnQYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBInQKGExpc3RVc2VyU2V0dGluZ3NSZXNwb25zZRIrCghzZXR0aW5ncxgBIAMoCzIZLm1lbW9zLmFwaS52MS5Vc2VyU2V0dGluZxIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkSEgoKdG90YWxfc2l6ZRgDIAEoBSLyAgoTUGVyc29uYWxBY2Nlc3NUb2tlbhIRCgRuYW1lGAEgASgJQgPgQQgSGAoLZGVzY3JpcHRpb24YAiABKAlCA+BBARIzCgpjcmVhdGVkX2F0GAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDEjMKCmV4cGlyZXNfYXQYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQESNQoMbGFzdF91c2VkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDOowB6kGIAQogbWVtb3MuYXBpLnYxL1BlcnNvbmFsQWNjZXNzVG9rZW4SOXVzZXJzL3t1c2VyfS9wZXJzb25hbEFjY2Vzc1Rva2Vucy97cGVyc29uYWxfYWNjZXNzX3Rva2VufSoUcGVyc29uYWxBY2Nlc3NUb2tlbnMyE3BlcnNvbmFsQWNjZXNzVG9rZW4ifQofTGlzdFBlcnNvbmFsQWNjZXNzVG9rZW5zUmVxdWVzdBIpCgZwYXJlbnQYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBIpIBCiBMaXN0UGVyc29uYWxBY2Nlc3NUb2tlbnNSZXNwb25zZRJBChZwZXJzb25hbF9hY2Nlc3NfdG9rZW5zGAEgAygLMiEubWVtb3MuYXBpLnYxLlBlcnNvbmFsQWNjZXNzVG9rZW4SFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJEhIKCnRvdGFsX3NpemUYAyABKAUihQEKIENyZWF0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXF1ZXN0EikKBnBhcmVudBgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvVXNlchIYCgtkZXNjcmlwdGlvbhgCIAEoCUID4EEBEhwKD2V4cGlyZXNfaW5fZGF5cxgDIAEoBUID4EEBInQKIUNyZWF0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXNwb25zZRJAChVwZXJzb25hbF9hY2Nlc3NfdG9rZW4YASABKAsyIS5tZW1vcy5hcGkudjEuUGVyc29uYWxBY2Nlc3NUb2tlbhINCgV0b2tlbhgCIAEoCSJaCiBEZWxldGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVxdWVzdBI2CgRuYW1lGAEgASgJQijgQQL6QSIKIG1lbW9zLmFwaS52MS9QZXJzb25hbEFjY2Vzc1Rva2VuIqoBCgtVc2VyV2ViaG9vaxIMCgRuYW1lGAEgASgJEgsKA3VybBgCIAEoCRIUCgxkaXNwbGF5X25hbWUYAyABKAkSNAoLY3JlYXRlX3RpbWUYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQMSNAoLdXBkYXRlX3RpbWUYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQMiLgoXTGlzdFVzZXJXZWJob29rc1JlcXVlc3QSEwoGcGFyZW50GAEgASgJQgPgQQIiRwoYTGlzdFVzZXJXZWJob29rc1Jlc3BvbnNlEisKCHdlYmhvb2tzGAEgAygLMhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rImAKGENyZWF0ZVVzZXJXZWJob29rUmVxdWVzdBITCgZwYXJlbnQYASABKAlCA+BBAhIvCgd3ZWJob29rGAIgASgLMhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rQgPgQQIifAoYVXBkYXRlVXNlcldlYmhvb2tSZXF1ZXN0Ei8KB3dlYmhvb2sYASABKAsyGS5tZW1vcy5hcGkudjEuVXNlcldlYmhvb2tCA+BBAhIvCgt1cGRhdGVfbWFzaxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE1hc2siLQoYRGVsZXRlVXNlcldlYmhvb2tSZXF1ZXN0EhEKBG5hbWUYASABKAlCA+BBAiLwBAoQVXNlck5vdGlmaWNhdGlvbhIUCgRuYW1lGAEgASgJQgbgQQPgQQgSKQoGc2VuZGVyGAIgASgJQhngQQP6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEjoKBnN0YXR1cxgDIAEoDjIlLm1lbW9zLmFwaS52MS5Vc2VyTm90aWZpY2F0aW9uLlN0YXR1c0ID4EEBEjQKC2NyZWF0ZV90aW1lGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDEjYKBHR5cGUYBSABKA4yIy5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbi5UeXBlQgPgQQMSTgoMbWVtb19jb21tZW50GAYgASgLMjEubWVtb3MuYXBpLnYxLlVzZXJOb3RpZmljYXRpb24uTWVtb0NvbW1lbnRQYXlsb2FkQgPgQQNIABo4ChJNZW1vQ29tbWVudFBheWxvYWQSDAoEbWVtbxgBIAEoCRIUCgxyZWxhdGVkX21lbW8YAiABKAkiOgoGU3RhdHVzEhYKElNUQVRVU19VTlNQRUNJRklFRBAAEgoKBlVOUkVBRBABEgwKCEFSQ0hJVkVEEAIiLgoEVHlwZRIUChBUWVBFX1VOU1BFQ0lGSUVEEAASEAoMTUVNT19DT01NRU5UEAE6cOpBbQodbWVtb3MuYXBpLnYxL1VzZXJOb3RpZmljYXRpb24SKXVzZXJzL3t1c2VyfS9ub3RpZmljYXRpb25zL3tub3RpZmljYXRpb259GgRuYW1lKg1ub3RpZmljYXRpb25zMgxub3RpZmljYXRpb25CCQoHcGF5bG9hZCKPAQocTGlzdFVzZXJOb3RpZmljYXRpb25zUmVxdWVzdBIpCgZwYXJlbnQYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBEhMKBmZpbHRlchgEIAEoCUID4EEBIm8KHUxpc3RVc2VyTm90aWZpY2F0aW9uc1Jlc3BvbnNlEjUKDW5vdGlmaWNhdGlvbnMYASADKAsyHi5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbhIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkikAEKHVVwZGF0ZVVzZXJOb3RpZmljYXRpb25SZXF1ZXN0EjkKDG5vdGlmaWNhdGlvbhgBIAEoCzIeLm1lbW9zLmFwaS52MS5Vc2VyTm90aWZpY2F0aW9uQgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQIiVAodRGVsZXRlVXNlck5vdGlmaWNhdGlvblJlcXVlc3QSMwoEbmFtZRgBIAEoCUIl4EEC+kEfCh1tZW1vcy5hcGkudjEvVXNlck5vdGlmaWNhdGlvbjKDFwoLVXNlclNlcnZpY2USYwoJTGlzdFVzZXJzEh4ubWVtb3MuYXBpLnYxLkxpc3RVc2Vyc1JlcXVlc3QaHy5tZW1vcy5hcGkudjEuTGlzdFVzZXJzUmVzcG9uc2UiFYLT5JMCDxINL2FwaS92MS91c2VycxJiCgdHZXRVc2VyEhwubWVtb3MuYXBpLnYxLkdldFVzZXJSZXF1ZXN0GhIubWVtb3MuYXBpLnYxLlVzZXIiJdpBBG5hbWWC0+STAhgSFi9hcGkvdjEve25hbWU9dXNlcnMvKn0SZQoKQ3JlYXRlVXNlchIfLm1lbW9zLmFwaS52MS5DcmVhdGVVc2VyUmVxdWVzdBoSLm1lbW9zLmFwaS52MS5Vc2VyIiLaQQR1c2VygtPkkwIVOgR1c2VyIg0vYXBpL3YxL3VzZXJzEn8KClVwZGF0ZVVzZXISHy5tZW1vcy5hcGkudjEuVXBkYXRlVXNlclJlcXVlc3QaEi5tZW1vcy5hcGkudjEuVXNlciI82kEQdXNlcix1cGRhdGVfbWFza4LT5JMCIzoEdXNlcjIbL2FwaS92MS97dXNlci5uYW1lPXVzZXJzLyp9EmwKCkRlbGV0ZVVzZXISHy5tZW1vcy5hcGkudjEuRGVsZXRlVXNlclJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiJdpBBG5hbWWC0+STAhgqFi9hcGkvdjEve25hbWU9dXNlcnMvKn0SfgoQTGlzdEFsbFVzZXJTdGF0cxIlLm1lbW9zLmFwaS52MS5MaXN0QWxsVXNlclN0YXRzUmVxdWVzdBomLm1lbW9zLmFwaS52MS5MaXN0QWxsVXNlclN0YXRzUmVzcG9uc2UiG4LT5JMCFRITL2FwaS92MS91c2VyczpzdGF0cxJ6CgxHZXRVc2VyU3RhdHMSIS5tZW1vcy5hcGkudjEuR2V0VXNlclN0YXRzUmVxdWVzdBoXLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMiLtpBBG5hbWWC0+STAiESHy9hcGkvdjEve25hbWU9dXNlcnMvKn06Z2V0U3RhdHMSggEKDkdldFVzZXJTZXR0aW5nEiMubWVtb3MuYXBpLnYxLkdldFVzZXJTZXR0aW5nUmVxdWVzdBoZLm1lbW9zLmFwaS52MS5Vc2VyU2V0dGluZyIw2kEEbmFtZYLT5JMCIxIhL2FwaS92MS97bmFtZT11c2Vycy8qL3NldHRpbmdzLyp9EqgBChFVcGRhdGVVc2VyU2V0dGluZxImLm1lbW9zLmFwaS52MS5VcGRhdGVVc2VyU2V0dGluZ1JlcXVlc3QaGS5tZW1vcy5hcGkudjEuVXNlclNldHRpbmciUNpBE3NldHRpbmcsdXBkYXRlX21hc2uC0+STAjQ6B3NldHRpbmcyKS9hcGkvdjEve3NldHRpbmcubmFtZT11c2Vycy8qL3NldHRpbmdzLyp9EpUBChBMaXN0VXNlclNldHRpbmdzEiUubWVtb3MuYXBpLnYxLkxpc3RVc2VyU2V0dGluZ3NSZXF1ZXN0GiYubWVtb3MuYXBpLnYxLkxpc3RVc2VyU2V0dGluZ3NSZXNwb25zZSIy2kEGcGFyZW50gtPkkwIjEiEvYXBpL3YxL3twYXJlbnQ9dXNlcnMvKn0vc2V0dGluZ3MSuQEKGExpc3RQZXJzb25hbEFjY2Vzc1Rva2VucxItLm1lbW9zLmFwaS52MS5MaXN0UGVyc29uYWxBY2Nlc3NUb2tlbnNSZXF1ZXN0Gi4ubWVtb3MuYXBpLnYxLkxpc3RQZXJzb25hbEFjY2Vzc1Rva2Vuc1Jlc3BvbnNlIj7aQQZwYXJlbnSC0+STAi8SLS9hcGkvdjEve3BhcmVudD11c2Vycy8qfS9wZXJzb25hbEFjY2Vzc1Rva2VucxK2AQoZQ3JlYXRlUGVyc29uYWxBY2Nlc3NUb2tlbhIuLm1lbW9zLmFwaS52MS5DcmVhdGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVxdWVzdBovLm1lbW9zLmFwaS52MS5DcmVhdGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVzcG9uc2UiOILT5JMCMjoBKiItL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3BlcnNvbmFsQWNjZXNzVG9rZW5zEqEBChlEZWxldGVQZXJzb25hbEFjY2Vzc1Rva2VuEi4ubWVtb3MuYXBpLnYxLkRlbGV0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IjzaQQRuYW1lgtPkkwIvKi0vYXBpL3YxL3tuYW1lPXVzZXJzLyovcGVyc29uYWxBY2Nlc3NUb2tlbnMvKn0SlQEKEExpc3RVc2VyV2ViaG9va3MSJS5tZW1vcy5hcGkudjEuTGlzdFVzZXJXZWJob29rc1JlcXVlc3QaJi5tZW1vcy5hcGkudjEuTGlzdFVzZXJXZWJob29rc1Jlc3BvbnNlIjLaQQZwYXJlbnSC0+STAiMSIS9hcGkvdjEve3BhcmVudD11c2Vycy8qfS93ZWJob29rcxKbAQoRQ3JlYXRlVXNlcldlYmhvb2sSJi5tZW1vcy5hcGkudjEuQ3JlYXRlVXNlcldlYmhvb2tSZXF1ZXN0GhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rIkPaQQ5wYXJlbnQsd2ViaG9va4LT5JMCLDoHd2ViaG9vayIhL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3dlYmhvb2tzEqgBChFVcGRhdGVVc2VyV2ViaG9vaxImLm1lbW9zLmFwaS52MS5VcGRhdGVVc2VyV2ViaG9va1JlcXVlc3QaGS5tZW1vcy5hcGkudjEuVXNlcldlYmhvb2siUNpBE3dlYmhvb2ssdXBkYXRlX21hc2uC0+STAjQ6B3dlYmhvb2syKS9hcGkvdjEve3dlYmhvb2submFtZT11c2Vycy8qL3dlYmhvb2tzLyp9EoUBChFEZWxldGVVc2VyV2ViaG9vaxImLm1lbW9zLmFwaS52MS5EZWxldGVVc2VyV2ViaG9va1JlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiMNpBBG5hbWWC0+STAiMqIS9hcGkvdjEve25hbWU9dXNlcnMvKi93ZWJob29rcy8qfRKpAQoVTGlzdFVzZXJOb3RpZmljYXRpb25zEioubWVtb3MuYXBpLnYxLkxpc3RVc2VyTm90aWZpY2F0aW9uc1JlcXVlc3QaKy5tZW1vcy5hcGkudjEuTGlzdFVzZXJOb3RpZmljYXRpb25zUmVzcG9uc2UiN9pBBnBhcmVudILT5JMCKBImL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L25vdGlmaWNhdGlvbnMSywEKFlVwZGF0ZVVzZXJOb3RpZmljYXRpb24SKy5tZW1vcy5hcGkudjEuVXBkYXRlVXNlck5vdGlmaWNhdGlvblJlcXVlc3QaHi5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbiJk2kEYbm90aWZpY2F0aW9uLHVwZGF0ZV9tYXNrgtPkkwJDOgxub3RpZmljYXRpb24yMy9hcGkvdjEve25vdGlmaWNhdGlvbi5uYW1lPXVzZXJzLyovbm90aWZpY2F0aW9ucy8qfRKUAQoWRGVsZXRlVXNlck5vdGlmaWNhdGlvbhIrLm1lbW9zLmFwaS52MS5EZWxldGVVc2VyTm90aWZpY2F0aW9uUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSI12kEEbmFtZYLT5JMCKComL2FwaS92MS97bmFtZT11c2Vycy8qL25vdGlmaWNhdGlvbnMvKn1CqAEKEGNvbS5tZW1vcy5hcGkudjFCEFVzZXJTZXJ2aWNlUHJvdG9QAVowZ2l0aHViLmNvbS91c2VtZW1vcy9tZW1vcy9wcm90by9nZW4vYXBpL3YxO2FwaXYxogIDTUFYqgIMTWVtb3MuQXBpLlYxygIMTWVtb3NcQXBpXFYx4gIYTWVtb3NcQXBpXFYxXEdQQk1ldGFkYXRh6gIOTWVtb3M6OkFwaTo6VjFiBnByb3RvMw", [file_api_v1_common, file_google_api_annotations, file_google_api_client, file_google_api_field_behavior, file_google_api_resource, file_google_protobuf_empty, file_google_protobuf_field_mask, file_google_protobuf_timestamp]); fileDesc("ChlhcGkvdjEvdXNlcl9zZXJ2aWNlLnByb3RvEgxtZW1vcy5hcGkudjEi1gMKBFVzZXISEQoEbmFtZRgBIAEoCUID4EEIEioKBHJvbGUYAiABKA4yFy5tZW1vcy5hcGkudjEuVXNlci5Sb2xlQgPgQQISFQoIdXNlcm5hbWUYAyABKAlCA+BBAhISCgVlbWFpbBgEIAEoCUID4EEBEhkKDGRpc3BsYXlfbmFtZRgFIAEoCUID4EEBEhcKCmF2YXRhcl91cmwYBiABKAlCA+BBARIYCgtkZXNjcmlwdGlvbhgHIAEoCUID4EEBEhUKCHBhc3N3b3JkGAggASgJQgPgQQQSJwoFc3RhdGUYCSABKA4yEy5tZW1vcy5hcGkudjEuU3RhdGVCA+BBAhI0CgtjcmVhdGVfdGltZRgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAxI0Cgt1cGRhdGVfdGltZRgLIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAyIxCgRSb2xlEhQKEFJPTEVfVU5TUEVDSUZJRUQQABIJCgVBRE1JThACEggKBFVTRVIQAzo36kE0ChFtZW1vcy5hcGkudjEvVXNlchIMdXNlcnMve3VzZXJ9GgRuYW1lKgV1c2VyczIEdXNlciJzChBMaXN0VXNlcnNSZXF1ZXN0EhYKCXBhZ2Vfc2l6ZRgBIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAiABKAlCA+BBARITCgZmaWx0ZXIYAyABKAlCA+BBARIZCgxzaG93X2RlbGV0ZWQYBCABKAhCA+BBASJjChFMaXN0VXNlcnNSZXNwb25zZRIhCgV1c2VycxgBIAMoCzISLm1lbW9zLmFwaS52MS5Vc2VyEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRISCgp0b3RhbF9zaXplGAMgASgFIikKFEJhdGNoR2V0VXNlcnNSZXF1ZXN0EhEKCXVzZXJuYW1lcxgBIAMoCSI6ChVCYXRjaEdldFVzZXJzUmVzcG9uc2USIQoFdXNlcnMYASADKAsyEi5tZW1vcy5hcGkudjEuVXNlciJtCg5HZXRVc2VyUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEjIKCXJlYWRfbWFzaxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE1hc2tCA+BBASKIAQoRQ3JlYXRlVXNlclJlcXVlc3QSKAoEdXNlchgBIAEoCzISLm1lbW9zLmFwaS52MS5Vc2VyQgbgQQLgQQQSFAoHdXNlcl9pZBgCIAEoCUID4EEBEhoKDXZhbGlkYXRlX29ubHkYAyABKAhCA+BBARIXCgpyZXF1ZXN0X2lkGAQgASgJQgPgQQEijAEKEVVwZGF0ZVVzZXJSZXF1ZXN0EiUKBHVzZXIYASABKAsyEi5tZW1vcy5hcGkudjEuVXNlckID4EECEjQKC3VwZGF0ZV9tYXNrGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLkZpZWxkTWFza0ID4EECEhoKDWFsbG93X21pc3NpbmcYAyABKAhCA+BBASJQChFEZWxldGVVc2VyUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEhIKBWZvcmNlGAIgASgIQgPgQQEi2AMKCVVzZXJTdGF0cxIRCgRuYW1lGAEgASgJQgPgQQgSOwoXbWVtb19kaXNwbGF5X3RpbWVzdGFtcHMYAiADKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEj4KD21lbW9fdHlwZV9zdGF0cxgDIAEoCzIlLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMuTWVtb1R5cGVTdGF0cxI4Cgl0YWdfY291bnQYBCADKAsyJS5tZW1vcy5hcGkudjEuVXNlclN0YXRzLlRhZ0NvdW50RW50cnkSFAoMcGlubmVkX21lbW9zGAUgAygJEhgKEHRvdGFsX21lbW9fY291bnQYBiABKAUaLwoNVGFnQ291bnRFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAU6AjgBGl8KDU1lbW9UeXBlU3RhdHMSEgoKbGlua19jb3VudBgBIAEoBRISCgpjb2RlX2NvdW50GAIgASgFEhIKCnRvZG9fY291bnQYAyABKAUSEgoKdW5kb19jb3VudBgEIAEoBTo/6kE8ChZtZW1vcy5hcGkudjEvVXNlclN0YXRzEgx1c2Vycy97dXNlcn0qCXVzZXJTdGF0czIJdXNlclN0YXRzIj4KE0dldFVzZXJTdGF0c1JlcXVlc3QSJwoEbmFtZRgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvVXNlciIZChdMaXN0QWxsVXNlclN0YXRzUmVxdWVzdCJCChhMaXN0QWxsVXNlclN0YXRzUmVzcG9uc2USJgoFc3RhdHMYASADKAsyFy5tZW1vcy5hcGkudjEuVXNlclN0YXRzIuQDCgtVc2VyU2V0dGluZxIRCgRuYW1lGAEgASgJQgPgQQgSQwoPZ2VuZXJhbF9zZXR0aW5nGAIgASgLMigubWVtb3MuYXBpLnYxLlVzZXJTZXR0aW5nLkdlbmVyYWxTZXR0aW5nSAASRQoQd2ViaG9va3Nfc2V0dGluZxgFIAEoCzIpLm1lbW9zLmFwaS52MS5Vc2VyU2V0dGluZy5XZWJob29rc1NldHRpbmdIABpXCg5HZW5lcmFsU2V0dGluZxITCgZsb2NhbGUYASABKAlCA+BBARIcCg9tZW1vX3Zpc2liaWxpdHkYAyABKAlCA+BBARISCgV0aGVtZRgEIAEoCUID4EEBGj4KD1dlYmhvb2tzU2V0dGluZxIrCgh3ZWJob29rcxgBIAMoCzIZLm1lbW9zLmFwaS52MS5Vc2VyV2ViaG9vayI1CgNLZXkSEwoPS0VZX1VOU1BFQ0lGSUVEEAASCwoHR0VORVJBTBABEgwKCFdFQkhPT0tTEAQ6XepBWgoYbWVtb3MuYXBpLnYxL1VzZXJTZXR0aW5nEiN1c2Vycy97dXNlcm5hbWV9L3NldHRpbmdzL3tzZXR0aW5nfSoMdXNlclNldHRpbmdzMgt1c2VyU2V0dGluZ0IHCgV2YWx1ZSJHChVHZXRVc2VyU2V0dGluZ1JlcXVlc3QSLgoEbmFtZRgBIAEoCUIg4EEC+kEaChhtZW1vcy5hcGkudjEvVXNlclNldHRpbmcigQEKGFVwZGF0ZVVzZXJTZXR0aW5nUmVxdWVzdBIvCgdzZXR0aW5nGAEgASgLMhkubWVtb3MuYXBpLnYxLlVzZXJTZXR0aW5nQgPgQQISNAoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrQgPgQQIidQoXTGlzdFVzZXJTZXR0aW5nc1JlcXVlc3QSKQoGcGFyZW50GAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEhYKCXBhZ2Vfc2l6ZRgCIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAyABKAlCA+BBASJ0ChhMaXN0VXNlclNldHRpbmdzUmVzcG9uc2USKwoIc2V0dGluZ3MYASADKAsyGS5tZW1vcy5hcGkudjEuVXNlclNldHRpbmcSFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJEhIKCnRvdGFsX3NpemUYAyABKAUi8gIKE1BlcnNvbmFsQWNjZXNzVG9rZW4SEQoEbmFtZRgBIAEoCUID4EEIEhgKC2Rlc2NyaXB0aW9uGAIgASgJQgPgQQESMwoKY3JlYXRlZF9hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAxIzCgpleHBpcmVzX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEBEjUKDGxhc3RfdXNlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCA+BBAzqMAepBiAEKIG1lbW9zLmFwaS52MS9QZXJzb25hbEFjY2Vzc1Rva2VuEjl1c2Vycy97dXNlcn0vcGVyc29uYWxBY2Nlc3NUb2tlbnMve3BlcnNvbmFsX2FjY2Vzc190b2tlbn0qFHBlcnNvbmFsQWNjZXNzVG9rZW5zMhNwZXJzb25hbEFjY2Vzc1Rva2VuIn0KH0xpc3RQZXJzb25hbEFjY2Vzc1Rva2Vuc1JlcXVlc3QSKQoGcGFyZW50GAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEhYKCXBhZ2Vfc2l6ZRgCIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAyABKAlCA+BBASKSAQogTGlzdFBlcnNvbmFsQWNjZXNzVG9rZW5zUmVzcG9uc2USQQoWcGVyc29uYWxfYWNjZXNzX3Rva2VucxgBIAMoCzIhLm1lbW9zLmFwaS52MS5QZXJzb25hbEFjY2Vzc1Rva2VuEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRISCgp0b3RhbF9zaXplGAMgASgFIoUBCiBDcmVhdGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVxdWVzdBIpCgZwYXJlbnQYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL1VzZXISGAoLZGVzY3JpcHRpb24YAiABKAlCA+BBARIcCg9leHBpcmVzX2luX2RheXMYAyABKAVCA+BBASJ0CiFDcmVhdGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVzcG9uc2USQAoVcGVyc29uYWxfYWNjZXNzX3Rva2VuGAEgASgLMiEubWVtb3MuYXBpLnYxLlBlcnNvbmFsQWNjZXNzVG9rZW4SDQoFdG9rZW4YAiABKAkiWgogRGVsZXRlUGVyc29uYWxBY2Nlc3NUb2tlblJlcXVlc3QSNgoEbmFtZRgBIAEoCUIo4EEC+kEiCiBtZW1vcy5hcGkudjEvUGVyc29uYWxBY2Nlc3NUb2tlbiKqAQoLVXNlcldlYmhvb2sSDAoEbmFtZRgBIAEoCRILCgN1cmwYAiABKAkSFAoMZGlzcGxheV9uYW1lGAMgASgJEjQKC2NyZWF0ZV90aW1lGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDEjQKC3VwZGF0ZV90aW1lGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDIi4KF0xpc3RVc2VyV2ViaG9va3NSZXF1ZXN0EhMKBnBhcmVudBgBIAEoCUID4EECIkcKGExpc3RVc2VyV2ViaG9va3NSZXNwb25zZRIrCgh3ZWJob29rcxgBIAMoCzIZLm1lbW9zLmFwaS52MS5Vc2VyV2ViaG9vayJgChhDcmVhdGVVc2VyV2ViaG9va1JlcXVlc3QSEwoGcGFyZW50GAEgASgJQgPgQQISLwoHd2ViaG9vaxgCIAEoCzIZLm1lbW9zLmFwaS52MS5Vc2VyV2ViaG9va0ID4EECInwKGFVwZGF0ZVVzZXJXZWJob29rUmVxdWVzdBIvCgd3ZWJob29rGAEgASgLMhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rQgPgQQISLwoLdXBkYXRlX21hc2sYAiABKAsyGi5nb29nbGUucHJvdG9idWYuRmllbGRNYXNrIi0KGERlbGV0ZVVzZXJXZWJob29rUmVxdWVzdBIRCgRuYW1lGAEgASgJQgPgQQIiogcKEFVzZXJOb3RpZmljYXRpb24SFAoEbmFtZRgBIAEoCUIG4EED4EEIEikKBnNlbmRlchgCIAEoCUIZ4EED+kETChFtZW1vcy5hcGkudjEvVXNlchIsCgtzZW5kZXJfdXNlchgIIAEoCzISLm1lbW9zLmFwaS52MS5Vc2VyQgPgQQMSOgoGc3RhdHVzGAMgASgOMiUubWVtb3MuYXBpLnYxLlVzZXJOb3RpZmljYXRpb24uU3RhdHVzQgPgQQESNAoLY3JlYXRlX3RpbWUYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQMSNgoEdHlwZRgFIAEoDjIjLm1lbW9zLmFwaS52MS5Vc2VyTm90aWZpY2F0aW9uLlR5cGVCA+BBAxJOCgxtZW1vX2NvbW1lbnQYBiABKAsyMS5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbi5NZW1vQ29tbWVudFBheWxvYWRCA+BBA0gAEk4KDG1lbW9fbWVudGlvbhgHIAEoCzIxLm1lbW9zLmFwaS52MS5Vc2VyTm90aWZpY2F0aW9uLk1lbW9NZW50aW9uUGF5bG9hZEID4EEDSAAabAoSTWVtb0NvbW1lbnRQYXlsb2FkEgwKBG1lbW8YASABKAkSFAoMcmVsYXRlZF9tZW1vGAIgASgJEhQKDG1lbW9fc25pcHBldBgDIAEoCRIcChRyZWxhdGVkX21lbW9fc25pcHBldBgEIAEoCRpsChJNZW1vTWVudGlvblBheWxvYWQSDAoEbWVtbxgBIAEoCRIUCgxyZWxhdGVkX21lbW8YAiABKAkSFAoMbWVtb19zbmlwcGV0GAMgASgJEhwKFHJlbGF0ZWRfbWVtb19zbmlwcGV0GAQgASgJIjoKBlN0YXR1cxIWChJTVEFUVVNfVU5TUEVDSUZJRUQQABIKCgZVTlJFQUQQARIMCghBUkNISVZFRBACIkAKBFR5cGUSFAoQVFlQRV9VTlNQRUNJRklFRBAAEhAKDE1FTU9fQ09NTUVOVBABEhAKDE1FTU9fTUVOVElPThACOnDqQW0KHW1lbW9zLmFwaS52MS9Vc2VyTm90aWZpY2F0aW9uEil1c2Vycy97dXNlcn0vbm90aWZpY2F0aW9ucy97bm90aWZpY2F0aW9ufRoEbmFtZSoNbm90aWZpY2F0aW9uczIMbm90aWZpY2F0aW9uQgkKB3BheWxvYWQijwEKHExpc3RVc2VyTm90aWZpY2F0aW9uc1JlcXVlc3QSKQoGcGFyZW50GAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEhYKCXBhZ2Vfc2l6ZRgCIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAyABKAlCA+BBARITCgZmaWx0ZXIYBCABKAlCA+BBASJvCh1MaXN0VXNlck5vdGlmaWNhdGlvbnNSZXNwb25zZRI1Cg1ub3RpZmljYXRpb25zGAEgAygLMh4ubWVtb3MuYXBpLnYxLlVzZXJOb3RpZmljYXRpb24SFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJIpABCh1VcGRhdGVVc2VyTm90aWZpY2F0aW9uUmVxdWVzdBI5Cgxub3RpZmljYXRpb24YASABKAsyHi5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbkID4EECEjQKC3VwZGF0ZV9tYXNrGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLkZpZWxkTWFza0ID4EECIlQKHURlbGV0ZVVzZXJOb3RpZmljYXRpb25SZXF1ZXN0EjMKBG5hbWUYASABKAlCJeBBAvpBHwodbWVtb3MuYXBpLnYxL1VzZXJOb3RpZmljYXRpb24ygBgKC1VzZXJTZXJ2aWNlEmMKCUxpc3RVc2VycxIeLm1lbW9zLmFwaS52MS5MaXN0VXNlcnNSZXF1ZXN0Gh8ubWVtb3MuYXBpLnYxLkxpc3RVc2Vyc1Jlc3BvbnNlIhWC0+STAg8SDS9hcGkvdjEvdXNlcnMSewoNQmF0Y2hHZXRVc2VycxIiLm1lbW9zLmFwaS52MS5CYXRjaEdldFVzZXJzUmVxdWVzdBojLm1lbW9zLmFwaS52MS5CYXRjaEdldFVzZXJzUmVzcG9uc2UiIYLT5JMCGzoBKiIWL2FwaS92MS91c2VyczpiYXRjaEdldBJiCgdHZXRVc2VyEhwubWVtb3MuYXBpLnYxLkdldFVzZXJSZXF1ZXN0GhIubWVtb3MuYXBpLnYxLlVzZXIiJdpBBG5hbWWC0+STAhgSFi9hcGkvdjEve25hbWU9dXNlcnMvKn0SZQoKQ3JlYXRlVXNlchIfLm1lbW9zLmFwaS52MS5DcmVhdGVVc2VyUmVxdWVzdBoSLm1lbW9zLmFwaS52MS5Vc2VyIiLaQQR1c2VygtPkkwIVOgR1c2VyIg0vYXBpL3YxL3VzZXJzEn8KClVwZGF0ZVVzZXISHy5tZW1vcy5hcGkudjEuVXBkYXRlVXNlclJlcXVlc3QaEi5tZW1vcy5hcGkudjEuVXNlciI82kEQdXNlcix1cGRhdGVfbWFza4LT5JMCIzoEdXNlcjIbL2FwaS92MS97dXNlci5uYW1lPXVzZXJzLyp9EmwKCkRlbGV0ZVVzZXISHy5tZW1vcy5hcGkudjEuRGVsZXRlVXNlclJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiJdpBBG5hbWWC0+STAhgqFi9hcGkvdjEve25hbWU9dXNlcnMvKn0SfgoQTGlzdEFsbFVzZXJTdGF0cxIlLm1lbW9zLmFwaS52MS5MaXN0QWxsVXNlclN0YXRzUmVxdWVzdBomLm1lbW9zLmFwaS52MS5MaXN0QWxsVXNlclN0YXRzUmVzcG9uc2UiG4LT5JMCFRITL2FwaS92MS91c2VyczpzdGF0cxJ6CgxHZXRVc2VyU3RhdHMSIS5tZW1vcy5hcGkudjEuR2V0VXNlclN0YXRzUmVxdWVzdBoXLm1lbW9zLmFwaS52MS5Vc2VyU3RhdHMiLtpBBG5hbWWC0+STAiESHy9hcGkvdjEve25hbWU9dXNlcnMvKn06Z2V0U3RhdHMSggEKDkdldFVzZXJTZXR0aW5nEiMubWVtb3MuYXBpLnYxLkdldFVzZXJTZXR0aW5nUmVxdWVzdBoZLm1lbW9zLmFwaS52MS5Vc2VyU2V0dGluZyIw2kEEbmFtZYLT5JMCIxIhL2FwaS92MS97bmFtZT11c2Vycy8qL3NldHRpbmdzLyp9EqgBChFVcGRhdGVVc2VyU2V0dGluZxImLm1lbW9zLmFwaS52MS5VcGRhdGVVc2VyU2V0dGluZ1JlcXVlc3QaGS5tZW1vcy5hcGkudjEuVXNlclNldHRpbmciUNpBE3NldHRpbmcsdXBkYXRlX21hc2uC0+STAjQ6B3NldHRpbmcyKS9hcGkvdjEve3NldHRpbmcubmFtZT11c2Vycy8qL3NldHRpbmdzLyp9EpUBChBMaXN0VXNlclNldHRpbmdzEiUubWVtb3MuYXBpLnYxLkxpc3RVc2VyU2V0dGluZ3NSZXF1ZXN0GiYubWVtb3MuYXBpLnYxLkxpc3RVc2VyU2V0dGluZ3NSZXNwb25zZSIy2kEGcGFyZW50gtPkkwIjEiEvYXBpL3YxL3twYXJlbnQ9dXNlcnMvKn0vc2V0dGluZ3MSuQEKGExpc3RQZXJzb25hbEFjY2Vzc1Rva2VucxItLm1lbW9zLmFwaS52MS5MaXN0UGVyc29uYWxBY2Nlc3NUb2tlbnNSZXF1ZXN0Gi4ubWVtb3MuYXBpLnYxLkxpc3RQZXJzb25hbEFjY2Vzc1Rva2Vuc1Jlc3BvbnNlIj7aQQZwYXJlbnSC0+STAi8SLS9hcGkvdjEve3BhcmVudD11c2Vycy8qfS9wZXJzb25hbEFjY2Vzc1Rva2VucxK2AQoZQ3JlYXRlUGVyc29uYWxBY2Nlc3NUb2tlbhIuLm1lbW9zLmFwaS52MS5DcmVhdGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVxdWVzdBovLm1lbW9zLmFwaS52MS5DcmVhdGVQZXJzb25hbEFjY2Vzc1Rva2VuUmVzcG9uc2UiOILT5JMCMjoBKiItL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3BlcnNvbmFsQWNjZXNzVG9rZW5zEqEBChlEZWxldGVQZXJzb25hbEFjY2Vzc1Rva2VuEi4ubWVtb3MuYXBpLnYxLkRlbGV0ZVBlcnNvbmFsQWNjZXNzVG9rZW5SZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IjzaQQRuYW1lgtPkkwIvKi0vYXBpL3YxL3tuYW1lPXVzZXJzLyovcGVyc29uYWxBY2Nlc3NUb2tlbnMvKn0SlQEKEExpc3RVc2VyV2ViaG9va3MSJS5tZW1vcy5hcGkudjEuTGlzdFVzZXJXZWJob29rc1JlcXVlc3QaJi5tZW1vcy5hcGkudjEuTGlzdFVzZXJXZWJob29rc1Jlc3BvbnNlIjLaQQZwYXJlbnSC0+STAiMSIS9hcGkvdjEve3BhcmVudD11c2Vycy8qfS93ZWJob29rcxKbAQoRQ3JlYXRlVXNlcldlYmhvb2sSJi5tZW1vcy5hcGkudjEuQ3JlYXRlVXNlcldlYmhvb2tSZXF1ZXN0GhkubWVtb3MuYXBpLnYxLlVzZXJXZWJob29rIkPaQQ5wYXJlbnQsd2ViaG9va4LT5JMCLDoHd2ViaG9vayIhL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L3dlYmhvb2tzEqgBChFVcGRhdGVVc2VyV2ViaG9vaxImLm1lbW9zLmFwaS52MS5VcGRhdGVVc2VyV2ViaG9va1JlcXVlc3QaGS5tZW1vcy5hcGkudjEuVXNlcldlYmhvb2siUNpBE3dlYmhvb2ssdXBkYXRlX21hc2uC0+STAjQ6B3dlYmhvb2syKS9hcGkvdjEve3dlYmhvb2submFtZT11c2Vycy8qL3dlYmhvb2tzLyp9EoUBChFEZWxldGVVc2VyV2ViaG9vaxImLm1lbW9zLmFwaS52MS5EZWxldGVVc2VyV2ViaG9va1JlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiMNpBBG5hbWWC0+STAiMqIS9hcGkvdjEve25hbWU9dXNlcnMvKi93ZWJob29rcy8qfRKpAQoVTGlzdFVzZXJOb3RpZmljYXRpb25zEioubWVtb3MuYXBpLnYxLkxpc3RVc2VyTm90aWZpY2F0aW9uc1JlcXVlc3QaKy5tZW1vcy5hcGkudjEuTGlzdFVzZXJOb3RpZmljYXRpb25zUmVzcG9uc2UiN9pBBnBhcmVudILT5JMCKBImL2FwaS92MS97cGFyZW50PXVzZXJzLyp9L25vdGlmaWNhdGlvbnMSywEKFlVwZGF0ZVVzZXJOb3RpZmljYXRpb24SKy5tZW1vcy5hcGkudjEuVXBkYXRlVXNlck5vdGlmaWNhdGlvblJlcXVlc3QaHi5tZW1vcy5hcGkudjEuVXNlck5vdGlmaWNhdGlvbiJk2kEYbm90aWZpY2F0aW9uLHVwZGF0ZV9tYXNrgtPkkwJDOgxub3RpZmljYXRpb24yMy9hcGkvdjEve25vdGlmaWNhdGlvbi5uYW1lPXVzZXJzLyovbm90aWZpY2F0aW9ucy8qfRKUAQoWRGVsZXRlVXNlck5vdGlmaWNhdGlvbhIrLm1lbW9zLmFwaS52MS5EZWxldGVVc2VyTm90aWZpY2F0aW9uUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSI12kEEbmFtZYLT5JMCKComL2FwaS92MS97bmFtZT11c2Vycy8qL25vdGlmaWNhdGlvbnMvKn1CqAEKEGNvbS5tZW1vcy5hcGkudjFCEFVzZXJTZXJ2aWNlUHJvdG9QAVowZ2l0aHViLmNvbS91c2VtZW1vcy9tZW1vcy9wcm90by9nZW4vYXBpL3YxO2FwaXYxogIDTUFYqgIMTWVtb3MuQXBpLlYxygIMTWVtb3NcQXBpXFYx4gIYTWVtb3NcQXBpXFYxXEdQQk1ldGFkYXRh6gIOTWVtb3M6OkFwaTo6VjFiBnByb3RvMw", [file_api_v1_common, file_google_api_annotations, file_google_api_client, file_google_api_field_behavior, file_google_api_resource, file_google_protobuf_empty, file_google_protobuf_field_mask, file_google_protobuf_timestamp]);
/** /**
* @generated from message memos.api.v1.User * @generated from message memos.api.v1.User
...@@ -223,6 +223,40 @@ export type ListUsersResponse = Message<"memos.api.v1.ListUsersResponse"> & { ...@@ -223,6 +223,40 @@ export type ListUsersResponse = Message<"memos.api.v1.ListUsersResponse"> & {
export const ListUsersResponseSchema: GenMessage<ListUsersResponse> = /*@__PURE__*/ export const ListUsersResponseSchema: GenMessage<ListUsersResponse> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 2); messageDesc(file_api_v1_user_service, 2);
/**
* @generated from message memos.api.v1.BatchGetUsersRequest
*/
export type BatchGetUsersRequest = Message<"memos.api.v1.BatchGetUsersRequest"> & {
/**
* @generated from field: repeated string usernames = 1;
*/
usernames: string[];
};
/**
* Describes the message memos.api.v1.BatchGetUsersRequest.
* Use `create(BatchGetUsersRequestSchema)` to create a new message.
*/
export const BatchGetUsersRequestSchema: GenMessage<BatchGetUsersRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 3);
/**
* @generated from message memos.api.v1.BatchGetUsersResponse
*/
export type BatchGetUsersResponse = Message<"memos.api.v1.BatchGetUsersResponse"> & {
/**
* @generated from field: repeated memos.api.v1.User users = 1;
*/
users: User[];
};
/**
* Describes the message memos.api.v1.BatchGetUsersResponse.
* Use `create(BatchGetUsersResponseSchema)` to create a new message.
*/
export const BatchGetUsersResponseSchema: GenMessage<BatchGetUsersResponse> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 4);
/** /**
* @generated from message memos.api.v1.GetUserRequest * @generated from message memos.api.v1.GetUserRequest
*/ */
...@@ -249,7 +283,7 @@ export type GetUserRequest = Message<"memos.api.v1.GetUserRequest"> & { ...@@ -249,7 +283,7 @@ export type GetUserRequest = Message<"memos.api.v1.GetUserRequest"> & {
* Use `create(GetUserRequestSchema)` to create a new message. * Use `create(GetUserRequestSchema)` to create a new message.
*/ */
export const GetUserRequestSchema: GenMessage<GetUserRequest> = /*@__PURE__*/ export const GetUserRequestSchema: GenMessage<GetUserRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 3); messageDesc(file_api_v1_user_service, 5);
/** /**
* @generated from message memos.api.v1.CreateUserRequest * @generated from message memos.api.v1.CreateUserRequest
...@@ -292,7 +326,7 @@ export type CreateUserRequest = Message<"memos.api.v1.CreateUserRequest"> & { ...@@ -292,7 +326,7 @@ export type CreateUserRequest = Message<"memos.api.v1.CreateUserRequest"> & {
* Use `create(CreateUserRequestSchema)` to create a new message. * Use `create(CreateUserRequestSchema)` to create a new message.
*/ */
export const CreateUserRequestSchema: GenMessage<CreateUserRequest> = /*@__PURE__*/ export const CreateUserRequestSchema: GenMessage<CreateUserRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 4); messageDesc(file_api_v1_user_service, 6);
/** /**
* @generated from message memos.api.v1.UpdateUserRequest * @generated from message memos.api.v1.UpdateUserRequest
...@@ -325,7 +359,7 @@ export type UpdateUserRequest = Message<"memos.api.v1.UpdateUserRequest"> & { ...@@ -325,7 +359,7 @@ export type UpdateUserRequest = Message<"memos.api.v1.UpdateUserRequest"> & {
* Use `create(UpdateUserRequestSchema)` to create a new message. * Use `create(UpdateUserRequestSchema)` to create a new message.
*/ */
export const UpdateUserRequestSchema: GenMessage<UpdateUserRequest> = /*@__PURE__*/ export const UpdateUserRequestSchema: GenMessage<UpdateUserRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 5); messageDesc(file_api_v1_user_service, 7);
/** /**
* @generated from message memos.api.v1.DeleteUserRequest * @generated from message memos.api.v1.DeleteUserRequest
...@@ -352,7 +386,7 @@ export type DeleteUserRequest = Message<"memos.api.v1.DeleteUserRequest"> & { ...@@ -352,7 +386,7 @@ export type DeleteUserRequest = Message<"memos.api.v1.DeleteUserRequest"> & {
* Use `create(DeleteUserRequestSchema)` to create a new message. * Use `create(DeleteUserRequestSchema)` to create a new message.
*/ */
export const DeleteUserRequestSchema: GenMessage<DeleteUserRequest> = /*@__PURE__*/ export const DeleteUserRequestSchema: GenMessage<DeleteUserRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 6); messageDesc(file_api_v1_user_service, 8);
/** /**
* User statistics messages * User statistics messages
...@@ -409,7 +443,7 @@ export type UserStats = Message<"memos.api.v1.UserStats"> & { ...@@ -409,7 +443,7 @@ export type UserStats = Message<"memos.api.v1.UserStats"> & {
* Use `create(UserStatsSchema)` to create a new message. * Use `create(UserStatsSchema)` to create a new message.
*/ */
export const UserStatsSchema: GenMessage<UserStats> = /*@__PURE__*/ export const UserStatsSchema: GenMessage<UserStats> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 7); messageDesc(file_api_v1_user_service, 9);
/** /**
* Memo type statistics. * Memo type statistics.
...@@ -443,7 +477,7 @@ export type UserStats_MemoTypeStats = Message<"memos.api.v1.UserStats.MemoTypeSt ...@@ -443,7 +477,7 @@ export type UserStats_MemoTypeStats = Message<"memos.api.v1.UserStats.MemoTypeSt
* Use `create(UserStats_MemoTypeStatsSchema)` to create a new message. * Use `create(UserStats_MemoTypeStatsSchema)` to create a new message.
*/ */
export const UserStats_MemoTypeStatsSchema: GenMessage<UserStats_MemoTypeStats> = /*@__PURE__*/ export const UserStats_MemoTypeStatsSchema: GenMessage<UserStats_MemoTypeStats> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 7, 0); messageDesc(file_api_v1_user_service, 9, 0);
/** /**
* @generated from message memos.api.v1.GetUserStatsRequest * @generated from message memos.api.v1.GetUserStatsRequest
...@@ -463,7 +497,7 @@ export type GetUserStatsRequest = Message<"memos.api.v1.GetUserStatsRequest"> & ...@@ -463,7 +497,7 @@ export type GetUserStatsRequest = Message<"memos.api.v1.GetUserStatsRequest"> &
* Use `create(GetUserStatsRequestSchema)` to create a new message. * Use `create(GetUserStatsRequestSchema)` to create a new message.
*/ */
export const GetUserStatsRequestSchema: GenMessage<GetUserStatsRequest> = /*@__PURE__*/ export const GetUserStatsRequestSchema: GenMessage<GetUserStatsRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 8); messageDesc(file_api_v1_user_service, 10);
/** /**
* This endpoint doesn't take any parameters. * This endpoint doesn't take any parameters.
...@@ -478,7 +512,7 @@ export type ListAllUserStatsRequest = Message<"memos.api.v1.ListAllUserStatsRequ ...@@ -478,7 +512,7 @@ export type ListAllUserStatsRequest = Message<"memos.api.v1.ListAllUserStatsRequ
* Use `create(ListAllUserStatsRequestSchema)` to create a new message. * Use `create(ListAllUserStatsRequestSchema)` to create a new message.
*/ */
export const ListAllUserStatsRequestSchema: GenMessage<ListAllUserStatsRequest> = /*@__PURE__*/ export const ListAllUserStatsRequestSchema: GenMessage<ListAllUserStatsRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 9); messageDesc(file_api_v1_user_service, 11);
/** /**
* @generated from message memos.api.v1.ListAllUserStatsResponse * @generated from message memos.api.v1.ListAllUserStatsResponse
...@@ -497,7 +531,7 @@ export type ListAllUserStatsResponse = Message<"memos.api.v1.ListAllUserStatsRes ...@@ -497,7 +531,7 @@ export type ListAllUserStatsResponse = Message<"memos.api.v1.ListAllUserStatsRes
* Use `create(ListAllUserStatsResponseSchema)` to create a new message. * Use `create(ListAllUserStatsResponseSchema)` to create a new message.
*/ */
export const ListAllUserStatsResponseSchema: GenMessage<ListAllUserStatsResponse> = /*@__PURE__*/ export const ListAllUserStatsResponseSchema: GenMessage<ListAllUserStatsResponse> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 10); messageDesc(file_api_v1_user_service, 12);
/** /**
* User settings message * User settings message
...@@ -537,7 +571,7 @@ export type UserSetting = Message<"memos.api.v1.UserSetting"> & { ...@@ -537,7 +571,7 @@ export type UserSetting = Message<"memos.api.v1.UserSetting"> & {
* Use `create(UserSettingSchema)` to create a new message. * Use `create(UserSettingSchema)` to create a new message.
*/ */
export const UserSettingSchema: GenMessage<UserSetting> = /*@__PURE__*/ export const UserSettingSchema: GenMessage<UserSetting> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 11); messageDesc(file_api_v1_user_service, 13);
/** /**
* General user settings configuration. * General user settings configuration.
...@@ -574,7 +608,7 @@ export type UserSetting_GeneralSetting = Message<"memos.api.v1.UserSetting.Gener ...@@ -574,7 +608,7 @@ export type UserSetting_GeneralSetting = Message<"memos.api.v1.UserSetting.Gener
* Use `create(UserSetting_GeneralSettingSchema)` to create a new message. * Use `create(UserSetting_GeneralSettingSchema)` to create a new message.
*/ */
export const UserSetting_GeneralSettingSchema: GenMessage<UserSetting_GeneralSetting> = /*@__PURE__*/ export const UserSetting_GeneralSettingSchema: GenMessage<UserSetting_GeneralSetting> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 11, 0); messageDesc(file_api_v1_user_service, 13, 0);
/** /**
* User webhooks configuration. * User webhooks configuration.
...@@ -595,7 +629,7 @@ export type UserSetting_WebhooksSetting = Message<"memos.api.v1.UserSetting.Webh ...@@ -595,7 +629,7 @@ export type UserSetting_WebhooksSetting = Message<"memos.api.v1.UserSetting.Webh
* Use `create(UserSetting_WebhooksSettingSchema)` to create a new message. * Use `create(UserSetting_WebhooksSettingSchema)` to create a new message.
*/ */
export const UserSetting_WebhooksSettingSchema: GenMessage<UserSetting_WebhooksSetting> = /*@__PURE__*/ export const UserSetting_WebhooksSettingSchema: GenMessage<UserSetting_WebhooksSetting> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 11, 1); messageDesc(file_api_v1_user_service, 13, 1);
/** /**
* Enumeration of user setting keys. * Enumeration of user setting keys.
...@@ -627,7 +661,7 @@ export enum UserSetting_Key { ...@@ -627,7 +661,7 @@ export enum UserSetting_Key {
* Describes the enum memos.api.v1.UserSetting.Key. * Describes the enum memos.api.v1.UserSetting.Key.
*/ */
export const UserSetting_KeySchema: GenEnum<UserSetting_Key> = /*@__PURE__*/ export const UserSetting_KeySchema: GenEnum<UserSetting_Key> = /*@__PURE__*/
enumDesc(file_api_v1_user_service, 11, 0); enumDesc(file_api_v1_user_service, 13, 0);
/** /**
* @generated from message memos.api.v1.GetUserSettingRequest * @generated from message memos.api.v1.GetUserSettingRequest
...@@ -647,7 +681,7 @@ export type GetUserSettingRequest = Message<"memos.api.v1.GetUserSettingRequest" ...@@ -647,7 +681,7 @@ export type GetUserSettingRequest = Message<"memos.api.v1.GetUserSettingRequest"
* Use `create(GetUserSettingRequestSchema)` to create a new message. * Use `create(GetUserSettingRequestSchema)` to create a new message.
*/ */
export const GetUserSettingRequestSchema: GenMessage<GetUserSettingRequest> = /*@__PURE__*/ export const GetUserSettingRequestSchema: GenMessage<GetUserSettingRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 12); messageDesc(file_api_v1_user_service, 14);
/** /**
* @generated from message memos.api.v1.UpdateUserSettingRequest * @generated from message memos.api.v1.UpdateUserSettingRequest
...@@ -673,7 +707,7 @@ export type UpdateUserSettingRequest = Message<"memos.api.v1.UpdateUserSettingRe ...@@ -673,7 +707,7 @@ export type UpdateUserSettingRequest = Message<"memos.api.v1.UpdateUserSettingRe
* Use `create(UpdateUserSettingRequestSchema)` to create a new message. * Use `create(UpdateUserSettingRequestSchema)` to create a new message.
*/ */
export const UpdateUserSettingRequestSchema: GenMessage<UpdateUserSettingRequest> = /*@__PURE__*/ export const UpdateUserSettingRequestSchema: GenMessage<UpdateUserSettingRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 13); messageDesc(file_api_v1_user_service, 15);
/** /**
* Request message for ListUserSettings method. * Request message for ListUserSettings method.
...@@ -713,7 +747,7 @@ export type ListUserSettingsRequest = Message<"memos.api.v1.ListUserSettingsRequ ...@@ -713,7 +747,7 @@ export type ListUserSettingsRequest = Message<"memos.api.v1.ListUserSettingsRequ
* Use `create(ListUserSettingsRequestSchema)` to create a new message. * Use `create(ListUserSettingsRequestSchema)` to create a new message.
*/ */
export const ListUserSettingsRequestSchema: GenMessage<ListUserSettingsRequest> = /*@__PURE__*/ export const ListUserSettingsRequestSchema: GenMessage<ListUserSettingsRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 14); messageDesc(file_api_v1_user_service, 16);
/** /**
* Response message for ListUserSettings method. * Response message for ListUserSettings method.
...@@ -749,7 +783,7 @@ export type ListUserSettingsResponse = Message<"memos.api.v1.ListUserSettingsRes ...@@ -749,7 +783,7 @@ export type ListUserSettingsResponse = Message<"memos.api.v1.ListUserSettingsRes
* Use `create(ListUserSettingsResponseSchema)` to create a new message. * Use `create(ListUserSettingsResponseSchema)` to create a new message.
*/ */
export const ListUserSettingsResponseSchema: GenMessage<ListUserSettingsResponse> = /*@__PURE__*/ export const ListUserSettingsResponseSchema: GenMessage<ListUserSettingsResponse> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 15); messageDesc(file_api_v1_user_service, 17);
/** /**
* PersonalAccessToken represents a long-lived token for API/script access. * PersonalAccessToken represents a long-lived token for API/script access.
...@@ -800,7 +834,7 @@ export type PersonalAccessToken = Message<"memos.api.v1.PersonalAccessToken"> & ...@@ -800,7 +834,7 @@ export type PersonalAccessToken = Message<"memos.api.v1.PersonalAccessToken"> &
* Use `create(PersonalAccessTokenSchema)` to create a new message. * Use `create(PersonalAccessTokenSchema)` to create a new message.
*/ */
export const PersonalAccessTokenSchema: GenMessage<PersonalAccessToken> = /*@__PURE__*/ export const PersonalAccessTokenSchema: GenMessage<PersonalAccessToken> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 16); messageDesc(file_api_v1_user_service, 18);
/** /**
* @generated from message memos.api.v1.ListPersonalAccessTokensRequest * @generated from message memos.api.v1.ListPersonalAccessTokensRequest
...@@ -834,7 +868,7 @@ export type ListPersonalAccessTokensRequest = Message<"memos.api.v1.ListPersonal ...@@ -834,7 +868,7 @@ export type ListPersonalAccessTokensRequest = Message<"memos.api.v1.ListPersonal
* Use `create(ListPersonalAccessTokensRequestSchema)` to create a new message. * Use `create(ListPersonalAccessTokensRequestSchema)` to create a new message.
*/ */
export const ListPersonalAccessTokensRequestSchema: GenMessage<ListPersonalAccessTokensRequest> = /*@__PURE__*/ export const ListPersonalAccessTokensRequestSchema: GenMessage<ListPersonalAccessTokensRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 17); messageDesc(file_api_v1_user_service, 19);
/** /**
* @generated from message memos.api.v1.ListPersonalAccessTokensResponse * @generated from message memos.api.v1.ListPersonalAccessTokensResponse
...@@ -867,7 +901,7 @@ export type ListPersonalAccessTokensResponse = Message<"memos.api.v1.ListPersona ...@@ -867,7 +901,7 @@ export type ListPersonalAccessTokensResponse = Message<"memos.api.v1.ListPersona
* Use `create(ListPersonalAccessTokensResponseSchema)` to create a new message. * Use `create(ListPersonalAccessTokensResponseSchema)` to create a new message.
*/ */
export const ListPersonalAccessTokensResponseSchema: GenMessage<ListPersonalAccessTokensResponse> = /*@__PURE__*/ export const ListPersonalAccessTokensResponseSchema: GenMessage<ListPersonalAccessTokensResponse> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 18); messageDesc(file_api_v1_user_service, 20);
/** /**
* @generated from message memos.api.v1.CreatePersonalAccessTokenRequest * @generated from message memos.api.v1.CreatePersonalAccessTokenRequest
...@@ -901,7 +935,7 @@ export type CreatePersonalAccessTokenRequest = Message<"memos.api.v1.CreatePerso ...@@ -901,7 +935,7 @@ export type CreatePersonalAccessTokenRequest = Message<"memos.api.v1.CreatePerso
* Use `create(CreatePersonalAccessTokenRequestSchema)` to create a new message. * Use `create(CreatePersonalAccessTokenRequestSchema)` to create a new message.
*/ */
export const CreatePersonalAccessTokenRequestSchema: GenMessage<CreatePersonalAccessTokenRequest> = /*@__PURE__*/ export const CreatePersonalAccessTokenRequestSchema: GenMessage<CreatePersonalAccessTokenRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 19); messageDesc(file_api_v1_user_service, 21);
/** /**
* @generated from message memos.api.v1.CreatePersonalAccessTokenResponse * @generated from message memos.api.v1.CreatePersonalAccessTokenResponse
...@@ -928,7 +962,7 @@ export type CreatePersonalAccessTokenResponse = Message<"memos.api.v1.CreatePers ...@@ -928,7 +962,7 @@ export type CreatePersonalAccessTokenResponse = Message<"memos.api.v1.CreatePers
* Use `create(CreatePersonalAccessTokenResponseSchema)` to create a new message. * Use `create(CreatePersonalAccessTokenResponseSchema)` to create a new message.
*/ */
export const CreatePersonalAccessTokenResponseSchema: GenMessage<CreatePersonalAccessTokenResponse> = /*@__PURE__*/ export const CreatePersonalAccessTokenResponseSchema: GenMessage<CreatePersonalAccessTokenResponse> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 20); messageDesc(file_api_v1_user_service, 22);
/** /**
* @generated from message memos.api.v1.DeletePersonalAccessTokenRequest * @generated from message memos.api.v1.DeletePersonalAccessTokenRequest
...@@ -948,7 +982,7 @@ export type DeletePersonalAccessTokenRequest = Message<"memos.api.v1.DeletePerso ...@@ -948,7 +982,7 @@ export type DeletePersonalAccessTokenRequest = Message<"memos.api.v1.DeletePerso
* Use `create(DeletePersonalAccessTokenRequestSchema)` to create a new message. * Use `create(DeletePersonalAccessTokenRequestSchema)` to create a new message.
*/ */
export const DeletePersonalAccessTokenRequestSchema: GenMessage<DeletePersonalAccessTokenRequest> = /*@__PURE__*/ export const DeletePersonalAccessTokenRequestSchema: GenMessage<DeletePersonalAccessTokenRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 21); messageDesc(file_api_v1_user_service, 23);
/** /**
* UserWebhook represents a webhook owned by a user. * UserWebhook represents a webhook owned by a user.
...@@ -998,7 +1032,7 @@ export type UserWebhook = Message<"memos.api.v1.UserWebhook"> & { ...@@ -998,7 +1032,7 @@ export type UserWebhook = Message<"memos.api.v1.UserWebhook"> & {
* Use `create(UserWebhookSchema)` to create a new message. * Use `create(UserWebhookSchema)` to create a new message.
*/ */
export const UserWebhookSchema: GenMessage<UserWebhook> = /*@__PURE__*/ export const UserWebhookSchema: GenMessage<UserWebhook> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 22); messageDesc(file_api_v1_user_service, 24);
/** /**
* @generated from message memos.api.v1.ListUserWebhooksRequest * @generated from message memos.api.v1.ListUserWebhooksRequest
...@@ -1018,7 +1052,7 @@ export type ListUserWebhooksRequest = Message<"memos.api.v1.ListUserWebhooksRequ ...@@ -1018,7 +1052,7 @@ export type ListUserWebhooksRequest = Message<"memos.api.v1.ListUserWebhooksRequ
* Use `create(ListUserWebhooksRequestSchema)` to create a new message. * Use `create(ListUserWebhooksRequestSchema)` to create a new message.
*/ */
export const ListUserWebhooksRequestSchema: GenMessage<ListUserWebhooksRequest> = /*@__PURE__*/ export const ListUserWebhooksRequestSchema: GenMessage<ListUserWebhooksRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 23); messageDesc(file_api_v1_user_service, 25);
/** /**
* @generated from message memos.api.v1.ListUserWebhooksResponse * @generated from message memos.api.v1.ListUserWebhooksResponse
...@@ -1037,7 +1071,7 @@ export type ListUserWebhooksResponse = Message<"memos.api.v1.ListUserWebhooksRes ...@@ -1037,7 +1071,7 @@ export type ListUserWebhooksResponse = Message<"memos.api.v1.ListUserWebhooksRes
* Use `create(ListUserWebhooksResponseSchema)` to create a new message. * Use `create(ListUserWebhooksResponseSchema)` to create a new message.
*/ */
export const ListUserWebhooksResponseSchema: GenMessage<ListUserWebhooksResponse> = /*@__PURE__*/ export const ListUserWebhooksResponseSchema: GenMessage<ListUserWebhooksResponse> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 24); messageDesc(file_api_v1_user_service, 26);
/** /**
* @generated from message memos.api.v1.CreateUserWebhookRequest * @generated from message memos.api.v1.CreateUserWebhookRequest
...@@ -1064,7 +1098,7 @@ export type CreateUserWebhookRequest = Message<"memos.api.v1.CreateUserWebhookRe ...@@ -1064,7 +1098,7 @@ export type CreateUserWebhookRequest = Message<"memos.api.v1.CreateUserWebhookRe
* Use `create(CreateUserWebhookRequestSchema)` to create a new message. * Use `create(CreateUserWebhookRequestSchema)` to create a new message.
*/ */
export const CreateUserWebhookRequestSchema: GenMessage<CreateUserWebhookRequest> = /*@__PURE__*/ export const CreateUserWebhookRequestSchema: GenMessage<CreateUserWebhookRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 25); messageDesc(file_api_v1_user_service, 27);
/** /**
* @generated from message memos.api.v1.UpdateUserWebhookRequest * @generated from message memos.api.v1.UpdateUserWebhookRequest
...@@ -1090,7 +1124,7 @@ export type UpdateUserWebhookRequest = Message<"memos.api.v1.UpdateUserWebhookRe ...@@ -1090,7 +1124,7 @@ export type UpdateUserWebhookRequest = Message<"memos.api.v1.UpdateUserWebhookRe
* Use `create(UpdateUserWebhookRequestSchema)` to create a new message. * Use `create(UpdateUserWebhookRequestSchema)` to create a new message.
*/ */
export const UpdateUserWebhookRequestSchema: GenMessage<UpdateUserWebhookRequest> = /*@__PURE__*/ export const UpdateUserWebhookRequestSchema: GenMessage<UpdateUserWebhookRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 26); messageDesc(file_api_v1_user_service, 28);
/** /**
* @generated from message memos.api.v1.DeleteUserWebhookRequest * @generated from message memos.api.v1.DeleteUserWebhookRequest
...@@ -1110,7 +1144,7 @@ export type DeleteUserWebhookRequest = Message<"memos.api.v1.DeleteUserWebhookRe ...@@ -1110,7 +1144,7 @@ export type DeleteUserWebhookRequest = Message<"memos.api.v1.DeleteUserWebhookRe
* Use `create(DeleteUserWebhookRequestSchema)` to create a new message. * Use `create(DeleteUserWebhookRequestSchema)` to create a new message.
*/ */
export const DeleteUserWebhookRequestSchema: GenMessage<DeleteUserWebhookRequest> = /*@__PURE__*/ export const DeleteUserWebhookRequestSchema: GenMessage<DeleteUserWebhookRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 27); messageDesc(file_api_v1_user_service, 29);
/** /**
* @generated from message memos.api.v1.UserNotification * @generated from message memos.api.v1.UserNotification
...@@ -1132,6 +1166,13 @@ export type UserNotification = Message<"memos.api.v1.UserNotification"> & { ...@@ -1132,6 +1166,13 @@ export type UserNotification = Message<"memos.api.v1.UserNotification"> & {
*/ */
sender: string; sender: string;
/**
* The sender user details.
*
* @generated from field: memos.api.v1.User sender_user = 8;
*/
senderUser?: User;
/** /**
* The status of the notification. * The status of the notification.
* *
...@@ -1162,6 +1203,12 @@ export type UserNotification = Message<"memos.api.v1.UserNotification"> & { ...@@ -1162,6 +1203,12 @@ export type UserNotification = Message<"memos.api.v1.UserNotification"> & {
*/ */
value: UserNotification_MemoCommentPayload; value: UserNotification_MemoCommentPayload;
case: "memoComment"; case: "memoComment";
} | {
/**
* @generated from field: memos.api.v1.UserNotification.MemoMentionPayload memo_mention = 7;
*/
value: UserNotification_MemoMentionPayload;
case: "memoMention";
} | { case: undefined; value?: undefined }; } | { case: undefined; value?: undefined };
}; };
...@@ -1170,7 +1217,7 @@ export type UserNotification = Message<"memos.api.v1.UserNotification"> & { ...@@ -1170,7 +1217,7 @@ export type UserNotification = Message<"memos.api.v1.UserNotification"> & {
* Use `create(UserNotificationSchema)` to create a new message. * Use `create(UserNotificationSchema)` to create a new message.
*/ */
export const UserNotificationSchema: GenMessage<UserNotification> = /*@__PURE__*/ export const UserNotificationSchema: GenMessage<UserNotification> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 28); messageDesc(file_api_v1_user_service, 30);
/** /**
* @generated from message memos.api.v1.UserNotification.MemoCommentPayload * @generated from message memos.api.v1.UserNotification.MemoCommentPayload
...@@ -1191,6 +1238,20 @@ export type UserNotification_MemoCommentPayload = Message<"memos.api.v1.UserNoti ...@@ -1191,6 +1238,20 @@ export type UserNotification_MemoCommentPayload = Message<"memos.api.v1.UserNoti
* @generated from field: string related_memo = 2; * @generated from field: string related_memo = 2;
*/ */
relatedMemo: string; relatedMemo: string;
/**
* Preview text of the comment memo.
*
* @generated from field: string memo_snippet = 3;
*/
memoSnippet: string;
/**
* Preview text of the related memo.
*
* @generated from field: string related_memo_snippet = 4;
*/
relatedMemoSnippet: string;
}; };
/** /**
...@@ -1198,7 +1259,49 @@ export type UserNotification_MemoCommentPayload = Message<"memos.api.v1.UserNoti ...@@ -1198,7 +1259,49 @@ export type UserNotification_MemoCommentPayload = Message<"memos.api.v1.UserNoti
* Use `create(UserNotification_MemoCommentPayloadSchema)` to create a new message. * Use `create(UserNotification_MemoCommentPayloadSchema)` to create a new message.
*/ */
export const UserNotification_MemoCommentPayloadSchema: GenMessage<UserNotification_MemoCommentPayload> = /*@__PURE__*/ export const UserNotification_MemoCommentPayloadSchema: GenMessage<UserNotification_MemoCommentPayload> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 28, 0); messageDesc(file_api_v1_user_service, 30, 0);
/**
* @generated from message memos.api.v1.UserNotification.MemoMentionPayload
*/
export type UserNotification_MemoMentionPayload = Message<"memos.api.v1.UserNotification.MemoMentionPayload"> & {
/**
* The memo that contains the mention.
* Format: memos/{memo}
*
* @generated from field: string memo = 1;
*/
memo: string;
/**
* The related parent memo when the mention was created in a comment.
* Format: memos/{memo}
*
* @generated from field: string related_memo = 2;
*/
relatedMemo: string;
/**
* Preview text of the memo that contains the mention.
*
* @generated from field: string memo_snippet = 3;
*/
memoSnippet: string;
/**
* Preview text of the related parent memo.
*
* @generated from field: string related_memo_snippet = 4;
*/
relatedMemoSnippet: string;
};
/**
* Describes the message memos.api.v1.UserNotification.MemoMentionPayload.
* Use `create(UserNotification_MemoMentionPayloadSchema)` to create a new message.
*/
export const UserNotification_MemoMentionPayloadSchema: GenMessage<UserNotification_MemoMentionPayload> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 30, 1);
/** /**
* @generated from enum memos.api.v1.UserNotification.Status * @generated from enum memos.api.v1.UserNotification.Status
...@@ -1224,7 +1327,7 @@ export enum UserNotification_Status { ...@@ -1224,7 +1327,7 @@ export enum UserNotification_Status {
* Describes the enum memos.api.v1.UserNotification.Status. * Describes the enum memos.api.v1.UserNotification.Status.
*/ */
export const UserNotification_StatusSchema: GenEnum<UserNotification_Status> = /*@__PURE__*/ export const UserNotification_StatusSchema: GenEnum<UserNotification_Status> = /*@__PURE__*/
enumDesc(file_api_v1_user_service, 28, 0); enumDesc(file_api_v1_user_service, 30, 0);
/** /**
* @generated from enum memos.api.v1.UserNotification.Type * @generated from enum memos.api.v1.UserNotification.Type
...@@ -1239,13 +1342,18 @@ export enum UserNotification_Type { ...@@ -1239,13 +1342,18 @@ export enum UserNotification_Type {
* @generated from enum value: MEMO_COMMENT = 1; * @generated from enum value: MEMO_COMMENT = 1;
*/ */
MEMO_COMMENT = 1, MEMO_COMMENT = 1,
/**
* @generated from enum value: MEMO_MENTION = 2;
*/
MEMO_MENTION = 2,
} }
/** /**
* Describes the enum memos.api.v1.UserNotification.Type. * Describes the enum memos.api.v1.UserNotification.Type.
*/ */
export const UserNotification_TypeSchema: GenEnum<UserNotification_Type> = /*@__PURE__*/ export const UserNotification_TypeSchema: GenEnum<UserNotification_Type> = /*@__PURE__*/
enumDesc(file_api_v1_user_service, 28, 1); enumDesc(file_api_v1_user_service, 30, 1);
/** /**
* @generated from message memos.api.v1.ListUserNotificationsRequest * @generated from message memos.api.v1.ListUserNotificationsRequest
...@@ -1280,7 +1388,7 @@ export type ListUserNotificationsRequest = Message<"memos.api.v1.ListUserNotific ...@@ -1280,7 +1388,7 @@ export type ListUserNotificationsRequest = Message<"memos.api.v1.ListUserNotific
* Use `create(ListUserNotificationsRequestSchema)` to create a new message. * Use `create(ListUserNotificationsRequestSchema)` to create a new message.
*/ */
export const ListUserNotificationsRequestSchema: GenMessage<ListUserNotificationsRequest> = /*@__PURE__*/ export const ListUserNotificationsRequestSchema: GenMessage<ListUserNotificationsRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 29); messageDesc(file_api_v1_user_service, 31);
/** /**
* @generated from message memos.api.v1.ListUserNotificationsResponse * @generated from message memos.api.v1.ListUserNotificationsResponse
...@@ -1302,7 +1410,7 @@ export type ListUserNotificationsResponse = Message<"memos.api.v1.ListUserNotifi ...@@ -1302,7 +1410,7 @@ export type ListUserNotificationsResponse = Message<"memos.api.v1.ListUserNotifi
* Use `create(ListUserNotificationsResponseSchema)` to create a new message. * Use `create(ListUserNotificationsResponseSchema)` to create a new message.
*/ */
export const ListUserNotificationsResponseSchema: GenMessage<ListUserNotificationsResponse> = /*@__PURE__*/ export const ListUserNotificationsResponseSchema: GenMessage<ListUserNotificationsResponse> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 30); messageDesc(file_api_v1_user_service, 32);
/** /**
* @generated from message memos.api.v1.UpdateUserNotificationRequest * @generated from message memos.api.v1.UpdateUserNotificationRequest
...@@ -1324,7 +1432,7 @@ export type UpdateUserNotificationRequest = Message<"memos.api.v1.UpdateUserNoti ...@@ -1324,7 +1432,7 @@ export type UpdateUserNotificationRequest = Message<"memos.api.v1.UpdateUserNoti
* Use `create(UpdateUserNotificationRequestSchema)` to create a new message. * Use `create(UpdateUserNotificationRequestSchema)` to create a new message.
*/ */
export const UpdateUserNotificationRequestSchema: GenMessage<UpdateUserNotificationRequest> = /*@__PURE__*/ export const UpdateUserNotificationRequestSchema: GenMessage<UpdateUserNotificationRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 31); messageDesc(file_api_v1_user_service, 33);
/** /**
* @generated from message memos.api.v1.DeleteUserNotificationRequest * @generated from message memos.api.v1.DeleteUserNotificationRequest
...@@ -1343,7 +1451,7 @@ export type DeleteUserNotificationRequest = Message<"memos.api.v1.DeleteUserNoti ...@@ -1343,7 +1451,7 @@ export type DeleteUserNotificationRequest = Message<"memos.api.v1.DeleteUserNoti
* Use `create(DeleteUserNotificationRequestSchema)` to create a new message. * Use `create(DeleteUserNotificationRequestSchema)` to create a new message.
*/ */
export const DeleteUserNotificationRequestSchema: GenMessage<DeleteUserNotificationRequest> = /*@__PURE__*/ export const DeleteUserNotificationRequestSchema: GenMessage<DeleteUserNotificationRequest> = /*@__PURE__*/
messageDesc(file_api_v1_user_service, 32); messageDesc(file_api_v1_user_service, 34);
/** /**
* @generated from service memos.api.v1.UserService * @generated from service memos.api.v1.UserService
...@@ -1359,6 +1467,16 @@ export const UserService: GenService<{ ...@@ -1359,6 +1467,16 @@ export const UserService: GenService<{
input: typeof ListUsersRequestSchema; input: typeof ListUsersRequestSchema;
output: typeof ListUsersResponseSchema; output: typeof ListUsersResponseSchema;
}, },
/**
* BatchGetUsers returns active users by usernames.
*
* @generated from rpc memos.api.v1.UserService.BatchGetUsers
*/
batchGetUsers: {
methodKind: "unary";
input: typeof BatchGetUsersRequestSchema;
output: typeof BatchGetUsersResponseSchema;
},
/** /**
* GetUser gets a user by username. * GetUser gets a user by username.
* Format: users/{username} (e.g., users/steven) * Format: users/{username} (e.g., users/steven)
......
import type { Root, Text } from "mdast";
import type { Node as UnistNode } from "unist";
import { visit } from "unist-util-visit";
import type { MentionNode, MentionNodeData } from "@/types/markdown";
const MAX_MENTION_LENGTH = 32;
function isMentionChar(char: string): boolean {
return /[A-Za-z0-9-]/.test(char);
}
function isMentionBoundary(char: string): boolean {
if (!char) return true;
return !isMentionChar(char);
}
type Segment = { type: "text"; value: string } | { type: "mention"; value: string };
export function parseMentionsFromText(text: string): Segment[] {
const segments: Segment[] = [];
const chars = [...text];
let i = 0;
while (i < chars.length) {
const prevChar = i > 0 ? chars[i - 1] : "";
if (chars[i] === "@" && isMentionBoundary(prevChar) && i + 1 < chars.length && isMentionChar(chars[i + 1])) {
let j = i + 1;
while (j < chars.length && isMentionChar(chars[j]) && j - i - 1 < MAX_MENTION_LENGTH) {
j++;
}
const username = chars.slice(i + 1, j).join("");
const hasLetterOrNumber = [...username].some((char) => /[A-Za-z0-9]/.test(char));
if (username && hasLetterOrNumber) {
segments.push({ type: "mention", value: username.toLowerCase() });
i = j;
continue;
}
}
let j = i + 1;
while (j < chars.length && chars[j] !== "@") {
j++;
}
segments.push({ type: "text", value: chars.slice(i, j).join("") });
i = j;
}
return segments;
}
export function extractMentionUsernames(text: string): string[] {
const usernames = parseMentionsFromText(text)
.filter((segment): segment is { type: "mention"; value: string } => segment.type === "mention")
.map((segment) => segment.value);
return Array.from(new Set(usernames));
}
function createMentionNode(username: string): MentionNode {
const data: MentionNodeData = {
hName: "span",
hProperties: {
className: "mention",
"data-mention": username,
},
hChildren: [{ type: "text", value: `@${username}` }],
};
return {
type: "mentionNode",
value: username,
data,
} as MentionNode;
}
export const remarkMention = () => {
return (tree: Root) => {
visit(tree, (node, index, parent) => {
if (node.type !== "text" || !parent || index === null) return;
const textNode = node as Text;
const segments = parseMentionsFromText(textNode.value);
if (segments.every((segment) => segment.type === "text")) {
return;
}
const newNodes = segments.map((segment) => {
if (segment.type === "mention") {
return createMentionNode(segment.value);
}
return {
type: "text",
value: segment.value,
} as Text;
});
if (typeof index === "number") {
(parent.children as UnistNode[]).splice(index, 1, ...(newNodes as UnistNode[]));
}
});
};
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment