157 Commits

Author SHA1 Message Date
07d1839112 chore: upgrade version to 0.4.1 2023-08-08 23:27:38 +08:00
876872f363 docs: add install browser extension 2023-08-08 23:21:15 +08:00
11e062549a fix: list shortcut api url 2023-08-08 23:00:05 +08:00
6a9fcb1c18 feat: implement list shortcuts v2 api 2023-08-08 22:48:10 +08:00
07365fda73 chore: update extension pnpm lock file 2023-08-08 22:01:07 +08:00
2264b64007 feat: implement extract shortcut name from url 2023-08-08 21:14:48 +08:00
bb389ad429 feat: update popup initial state handler 2023-08-08 20:53:15 +08:00
b6967abd08 chore: update air config 2023-08-08 20:52:37 +08:00
f886bd7eb8 feat: implement extension's popup and options 2023-08-08 20:16:14 +08:00
b638d9cdf4 chore: buf generate 2023-08-08 09:25:34 +08:00
8af0675247 chore: remove extension package action 2023-08-08 08:28:05 +08:00
fd09b18033 chore: fix extension test 2023-08-08 08:25:24 +08:00
129a9cf48c chore: add extension test action 2023-08-08 08:23:32 +08:00
feadf879dd chore: initial extension structure 2023-08-08 08:20:01 +08:00
9c134f4c8f chore: update access token list 2023-08-07 23:41:48 +08:00
b624576269 feat: implement shortcut service 2023-08-07 23:37:40 +08:00
2d980380e5 chore: update shortcut card padding 2023-08-07 20:22:40 +08:00
fda2a3436d chore: add copy button to access token 2023-08-07 20:08:47 +08:00
6f96e5e0c8 chore: update setting sections padding 2023-08-07 19:54:51 +08:00
dadf42c09b feat: allow to generate access token without expires time 2023-08-07 19:52:13 +08:00
e855f8c5ad chore: remove debug codes 2023-08-06 23:47:14 +08:00
01ec5900d4 feat: implement access tokens management in UI 2023-08-06 23:37:13 +08:00
850fbbaa36 chore: buf build es types 2023-08-06 21:43:34 +08:00
820b8fc379 chore: add expiration into create access token request 2023-08-06 21:33:16 +08:00
a90279221c feat: implement create&delete user access token api 2023-08-06 20:53:45 +08:00
ad988575b3 feat: implement get access tokens api 2023-08-06 20:25:23 +08:00
994a90c8fb chore: remove revoked field in access token 2023-08-06 14:28:35 +08:00
f33dcba284 chore: upgrade vite 2023-08-06 14:19:08 +08:00
84ddafeb84 feat: validate access token 2023-08-06 14:16:23 +08:00
d8903875d3 chore: update acl in api v2 2023-08-06 13:50:04 +08:00
fb3267d139 feat: add access tokens to user setting 2023-08-06 13:46:52 +08:00
aaed0a747f chore: update prettier setting 2023-08-05 21:28:50 +08:00
9a491e2a82 chore: upgrade version to 0.4.0 2023-08-03 23:02:35 +08:00
e798e5e82b chore: update demo screenshot 2023-08-03 23:01:23 +08:00
87841828ff chore: update shortcut detail style 2023-08-03 22:05:50 +08:00
f28d23eae7 feat: add analytic to shortcut detail 2023-08-03 21:56:12 +08:00
606652f7a2 chore: update shortcut compact style 2023-08-03 09:13:39 +08:00
6395b698b9 feat: add shortcut display mode 2023-08-03 00:20:11 +08:00
f83c21cc93 chore: update shortcut view 2023-08-02 23:48:54 +08:00
b365355610 chore: migrate part of shortcut store 2023-08-02 21:57:32 +08:00
98d4bb40b2 fix: title display 2023-08-02 21:56:44 +08:00
fcf5981b97 feat: migrate part of shortcut store to v1 2023-08-02 21:07:38 +08:00
977ac76928 chore: update 2023-08-02 20:13:42 +08:00
66f9c2b568 chore: update package.json 2023-08-02 20:00:16 +08:00
e3ce79917d chore: upgrade vite 2023-08-02 19:45:42 +08:00
61cec67ec0 chore: update pnpm lock file 2023-08-02 19:44:57 +08:00
d6dccb1f95 chore: update id type to int32 2023-08-02 08:41:56 +08:00
c26834e9cd chore: update api v1 user context name 2023-08-02 07:44:04 +08:00
59a75c89eb feat: implement part of user service 2023-08-02 07:35:36 +08:00
dfe47b9b7e feat: update jwt auth 2023-08-02 07:29:58 +08:00
759ca1c6fd chore: update response padding 2023-08-01 23:39:29 +08:00
74200f468c feat: add shortcut detail page 2023-08-01 23:19:17 +08:00
23d84299e4 chore: update max width 2023-08-01 20:35:25 +08:00
47e0fcd43c chore: remove extension folder 2023-08-01 20:07:04 +08:00
0c4ed55a76 chore: move title to additional fields 2023-07-31 23:34:19 +08:00
db842a2c78 chore: add sort import plugin 2023-07-31 23:25:38 +08:00
e6ece43231 feat: add title field to shortcut 2023-07-31 21:53:06 +08:00
714889433f chore: fix action path 2023-07-31 20:01:49 +08:00
80c6464208 chore: update github actions 2023-07-31 20:00:19 +08:00
1f9c87b81b feat: initial buf proto files 2023-07-31 19:57:31 +08:00
4cc2de8e82 chore: update demo link 2023-07-31 18:57:29 +08:00
fab3d0033c chore: fix view setting dropdown position 2023-07-31 07:24:35 +08:00
6f9df9dfd7 chore: update base font size 2023-07-31 00:16:34 +08:00
f5463af7db chore: fix tailwind linter warning 2023-07-29 15:51:01 +08:00
a44b6494bf chore: disallow to revert the last admin user 2023-07-29 15:12:19 +08:00
1ce4b91433 chore: tweak detail style 2023-07-29 14:59:16 +08:00
4139520181 chore: update default layout to grid 2023-07-29 12:24:23 +08:00
890bc27982 chore: upgrade version 2023-07-29 09:01:18 +08:00
a379614cd9 chore: update overflow style 2023-07-28 23:32:51 +08:00
c18bbfd0bb feat: add grid layout of shortcuts view 2023-07-28 23:20:36 +08:00
d798b2c5fb chore: update auth duration 2023-07-28 22:53:32 +08:00
4e3ca8ceb4 chore: fix App.tsx 2023-07-28 22:53:21 +08:00
96a68ab117 chore: update demo banner 2023-07-28 22:49:18 +08:00
0eea0a92db chore: fix navigator tags 2023-07-28 22:41:22 +08:00
4a47010608 chore: update live demo url 2023-07-28 21:17:03 +08:00
fa504a88e5 chore: update docker username 2023-07-28 00:20:51 +08:00
de51e1a8d3 chore: update navigator style 2023-07-27 23:42:12 +08:00
49cc1e9755 chore: update create shortcut dialog 2023-07-27 23:24:13 +08:00
ce5c4b65d3 chore: update seed data 2023-07-27 22:25:36 +08:00
cee6c7c401 feat: adjust buttons position 2023-07-27 22:21:18 +08:00
b6839d2b7d chore: fix seed data 2023-07-27 21:19:16 +08:00
0ebf03eb9b revert: chore: fix seed data 2023-07-27 21:19:08 +08:00
21eab35e45 chore: fix seed data 2023-07-27 21:13:13 +08:00
fd1168e1dc chore: add tags to seed data 2023-07-27 21:05:31 +08:00
5ee32d2e78 chore: tweak button style 2023-07-27 21:03:04 +08:00
2db9c1e850 chore: upgrade dependencies 2023-07-27 20:59:35 +08:00
953ec3dbc0 feat: implement navigator 2023-07-27 20:59:21 +08:00
fc28473aee chore: remove unreleased code 2023-07-27 20:20:47 +08:00
c42c543618 chore: tweak words 2023-07-27 20:06:34 +08:00
72106d13de chore: update slash description 2023-07-27 19:59:49 +08:00
6bbf2df8e0 chore: fix tag wrap style 2023-07-26 08:27:22 +08:00
d42d3fbe10 chore: upgrade version to 0.3.1 2023-07-24 23:22:47 +08:00
6dfccb9509 fix: escape link text to prevent XSS 2023-07-24 22:01:32 +08:00
66876452e1 chore: update seed data 2023-07-24 19:14:24 +08:00
6b107924aa chore: update badges in readme 2023-07-23 23:54:37 +08:00
b84620c057 docs: update readme 2023-07-23 23:49:51 +08:00
c30b6adb8e docs: add install guide 2023-07-23 23:42:36 +08:00
c8fea442d6 chore: fix width overflow 2023-07-23 23:20:34 +08:00
a36a99e53d chore: update search input 2023-07-23 02:12:15 +08:00
86078b097d chore: fix form event in firefox 2023-07-23 02:09:50 +08:00
11205566ac feat: add search input 2023-07-23 01:54:14 +08:00
709118464b chore: update shortcut view style 2023-07-23 01:53:17 +08:00
792b60c480 chore: update autofill in demo 2023-07-22 11:54:23 +08:00
1418fc2209 chore: update demo seed data 2023-07-22 11:43:58 +08:00
53c1d8fa91 chore: upgrade version to 0.3.0 2023-07-21 22:39:21 +08:00
b32fdbfc0a fix: icon style 2023-07-21 22:13:05 +08:00
db2aebcf57 chore: update create shortcut dialog 2023-07-21 22:06:06 +08:00
b4e23fc8a0 chore: update seed data 2023-07-21 21:50:59 +08:00
7ab66113ac fix: field name 2023-07-21 21:37:07 +08:00
2909676ed3 chore: update metadata tags 2023-07-21 21:35:37 +08:00
5af9236c19 chore: update 2023-07-21 21:30:54 +08:00
04c0f47559 feat: add inputs for og metadata 2023-07-21 21:27:26 +08:00
a91997683b feat: add open graph metadata field 2023-07-21 21:27:05 +08:00
014dd7d660 chore: update create shortcut dialog 2023-07-21 20:30:16 +08:00
a1b633e4db chore: update store statement execution 2023-07-19 22:33:30 +08:00
57496c9b46 chore: add loading view to analytics 2023-07-19 21:47:44 +08:00
c4f38f1de6 chore: add toast to reset button 2023-07-19 09:05:40 +08:00
e7cf0c2f79 feat: add view order setting 2023-07-17 22:43:15 +08:00
15ffd0738c chore: update button hover style 2023-07-17 21:14:57 +08:00
21ff8ba797 chore: code clean 2023-07-17 21:14:40 +08:00
b2ce071ef0 chore: update readme 2023-07-14 21:41:34 +08:00
65545c78c6 chore: add tooltip to buttons 2023-07-14 20:45:06 +08:00
4279151238 chore: upgrade version to v0.2.0 2023-07-14 20:42:10 +08:00
3d3f55a931 chore: update air script 2023-07-14 20:42:00 +08:00
85569c032a chore: remove duplicated create button 2023-07-14 20:41:53 +08:00
bd9daddaef chore: add demo mode 2023-07-14 18:14:14 +08:00
af31875e6a chore: fix role handler 2023-07-14 13:36:32 +08:00
a0766159f2 chore: update profile 2023-07-14 13:07:01 +08:00
316617c396 chore: tweak description 2023-07-14 00:44:44 +08:00
402b766872 chore: update greeting words 2023-07-12 07:30:24 +08:00
8fade614d2 chore: fix signout 2023-07-12 07:14:23 +08:00
d8c980f56f chore: add indexes 2023-07-12 00:08:17 +08:00
b36572c5be chore: rename to slash 2023-07-11 23:51:17 +08:00
fcd72e1f98 chore: update auth checks 2023-07-11 23:39:19 +08:00
1cbab78989 feat: add chrome extension 2023-07-11 08:37:27 +08:00
28df6e35fb chore: tweak styles 2023-07-10 23:47:32 +08:00
12172f11c0 chore: update styles 2023-07-10 23:35:23 +08:00
00c7abc38d feat: add visibility filter 2023-07-10 23:19:03 +08:00
0cceed51f8 feat: implement shortcut view analytics 2023-07-10 22:57:29 +08:00
d866d5b53b chore: move auth to apiv1 2023-07-10 21:40:55 +08:00
05bc21b660 chore: update view store 2023-07-10 21:34:13 +08:00
9455824a2d feat: add tag filter 2023-07-09 11:36:26 +08:00
0b659ba124 chore: add vacuum functions 2023-07-09 01:37:20 +08:00
d82d3701dd chore: update dialog titles 2023-07-09 01:06:21 +08:00
5db3506cba feat: add member list in setting 2023-07-09 00:45:26 +08:00
c00f7d0852 chore: update request skipper 2023-07-05 00:35:24 +08:00
d900ca060a chore: remove unused scripts 2023-07-05 00:23:07 +08:00
b179f7b441 chore: update demo.gif 2023-07-04 23:08:55 +08:00
506e740438 chore: update demo gif 2023-07-04 23:02:48 +08:00
731ad57fd2 chore: update shortcut view style 2023-07-04 22:52:18 +08:00
9fd7d6bd34 chore: add demo video 2023-07-04 22:52:09 +08:00
7ca5c92769 feat: add generate qrcode dialog 2023-07-04 22:30:41 +08:00
96d44bd651 chore: update header style 2023-07-04 21:23:11 +08:00
ee9e092129 chore: code clean 2023-07-04 21:22:47 +08:00
f0334d5755 chore: update list shortcut api 2023-07-04 21:22:40 +08:00
1084381bbf chore: update jwt middleware 2023-07-04 21:07:12 +08:00
7d90b47875 chore: update shortcut view 2023-07-04 20:53:07 +08:00
197 changed files with 20472 additions and 2137 deletions

View File

@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Thanks for taking the time to suggest an idea for Shortify!
Thanks for taking the time to suggest an idea for Slash!
- type: textarea
attributes:
label: Is your feature request related to a problem?

View File

@ -1,4 +1,4 @@
name: Test
name: Backend Test
on:
push:
@ -28,43 +28,6 @@ jobs:
args: -v
skip-cache: true
eslint-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
with:
version: 8
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18"
cache: pnpm
cache-dependency-path: "web/pnpm-lock.yaml"
- run: pnpm install
working-directory: web
- name: Run eslint check
run: pnpm lint
working-directory: web
frontend-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: "18"
cache: pnpm
cache-dependency-path: "web/pnpm-lock.yaml"
- run: pnpm install
working-directory: web
- name: Run frontend build
run: pnpm build
working-directory: web
go-tests:
runs-on: ubuntu-latest
steps:

View File

@ -24,7 +24,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: stevenlgtm
username: yourselfhosted
password: ${{ secrets.DOCKER_TOKEN }}
- name: Set up Docker Buildx
@ -41,4 +41,4 @@ jobs:
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: stevenlgtm/shortify:latest, stevenlgtm/shortify:${{ env.VERSION }}
tags: yourselfhosted/slash:latest, yourselfhosted/slash:${{ env.VERSION }}

View File

@ -16,7 +16,7 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: stevenlgtm
username: yourselfhosted
password: ${{ secrets.DOCKER_TOKEN }}
- name: Set up Docker Buildx
@ -34,4 +34,4 @@ jobs:
file: ./Dockerfile
platforms: linux/amd64
push: true
tags: stevenlgtm/shortify:test
tags: yourselfhosted/slash:test

49
.github/workflows/extension-test.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Extension Test
on:
push:
branches:
- main
- "release/v*.*.*"
pull_request:
branches: [main]
paths:
- "extension/**"
jobs:
eslint-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
with:
version: 8
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18"
cache: pnpm
cache-dependency-path: "extension/pnpm-lock.yaml"
- run: pnpm install
working-directory: extension
- name: Run eslint check
run: pnpm lint
working-directory: extension
extension-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: "18"
cache: pnpm
cache-dependency-path: "extension/pnpm-lock.yaml"
- run: pnpm install
working-directory: extension
- name: Run extension build
run: pnpm build
working-directory: extension

49
.github/workflows/frontend-test.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Frontend Test
on:
push:
branches:
- main
- "release/v*.*.*"
pull_request:
branches: [main]
paths:
- "web/**"
jobs:
eslint-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
with:
version: 8
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18"
cache: pnpm
cache-dependency-path: "web/pnpm-lock.yaml"
- run: pnpm install
working-directory: web
- name: Run eslint check
run: pnpm lint
working-directory: web
frontend-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: "18"
cache: pnpm
cache-dependency-path: "web/pnpm-lock.yaml"
- run: pnpm install
working-directory: web
- name: Run frontend build
run: pnpm build
working-directory: web

34
.github/workflows/proto-linter.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Proto linter
on:
push:
branches:
- main
- "release/v*.*.*"
pull_request:
branches:
- main
- "release/*.*.*"
paths:
- "proto/**"
jobs:
lint-protos:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup buf
uses: bufbuild/buf-setup-action@v1
- name: buf lint
uses: bufbuild/buf-lint-action@v1
with:
input: "proto"
- name: buf format
run: |
if [[ $(buf format -d) ]]; then
echo "Run 'buf format -w'"
exit 1
fi

2
.gitignore vendored
View File

@ -11,5 +11,3 @@ web/dist
build
.DS_Store
extension

View File

@ -17,18 +17,24 @@ WORKDIR /backend-build
COPY . .
COPY --from=frontend /frontend-build/dist ./server/dist
RUN go build -o shortify ./cmd/shortify/main.go
RUN CGO_ENABLED=0 go build -o slash ./cmd/slash/main.go
# Make workspace with above generated files.
FROM alpine:3.16 AS monolithic
WORKDIR /usr/local/shortify
WORKDIR /usr/local/slash
RUN apk add --no-cache tzdata
ENV TZ="UTC"
COPY --from=backend /backend-build/shortify /usr/local/shortify/
COPY --from=backend /backend-build/slash /usr/local/slash/
EXPOSE 5231
# Directory to store the data, which can be referenced as the mounting point.
RUN mkdir -p /var/opt/shortify
RUN mkdir -p /var/opt/slash
VOLUME /var/opt/slash
ENTRYPOINT ["./shortify", "--mode", "prod", "--port", "5231"]
ENV SLASH_MODE="prod"
ENV SLASH_PORT="5231"
ENTRYPOINT ["./slash"]

View File

@ -1,17 +1,28 @@
# Shortify
# Slash
<img align="right" src="./resources/logo.png" height="64px" alt="logo">
**Shortify** is a bookmarking and short link service that allows you to save and share links easily. It lets you store and categorize links, generate short URLs for easy sharing, search and filter your saved links, and access them from any device. It simplifies link organization, management, and collaboration, making it effortless to navigate and share web resources.
**Slash** is an open source, self-hosted bookmarks and link sharing platform. It allows you to organize your links with tags, and share them using custom shortened URLs. Slash also supports team sharing of link libraries for easy collaboration.
Let's Simplify, Share, and Save your links with **Shortify**.
<p>
<a href="https://discord.gg/QZqUuUAhDV"><img alt="Discord" src="https://img.shields.io/badge/discord-chat-5865f2?logo=discord&logoColor=f5f5f5" /></a>
<a href="https://hub.docker.com/r/yourselfhosted/slash"><img alt="Docker pull" src="https://img.shields.io/docker/pulls/yourselfhosted/slash.svg" /></a>
<a href="https://github.com/boojack/slash/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/boojack/slash?logo=github" /></a>
</p>
![demo](./resources/demo.png)
## Features
- Create customizable `/s/` short links for any URL.
- Share short links privately or with teammates.
- View analytics on link traffic and sources.
- Open source self-hosted solution.
## Deploy with Docker in seconds
```bash
docker run -d --name shortify -p 5231:5231 -v ~/.shortify/:/var/opt/shortify stevenlgtm/shortify:latest
docker run -d --name slash -p 5231:5231 -v ~/.slash/:/var/opt/slash yourselfhosted/slash:latest
```
## Demo
![demo](./resources/demo.gif)
Learn more in [Self-hosting Slash with Docker](https://github.com/boojack/slash/blob/main/docs/install.md).

64
api/auth/auth.go Normal file
View File

@ -0,0 +1,64 @@
package auth
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v4"
)
const (
// issuer is the issuer of the jwt token.
Issuer = "slash"
// Signing key section. For now, this is only used for signing, not for verifying since we only
// have 1 version. But it will be used to maintain backward compatibility if we change the signing mechanism.
KeyID = "v1"
// AccessTokenAudienceName is the audience name of the access token.
AccessTokenAudienceName = "user.access-token"
AccessTokenDuration = 7 * 24 * time.Hour
// CookieExpDuration expires slightly earlier than the jwt expiration. Client would be logged out if the user
// cookie expires, thus the client would always logout first before attempting to make a request with the expired jwt.
CookieExpDuration = AccessTokenDuration - 1*time.Minute
// AccessTokenCookieName is the cookie name of access token.
AccessTokenCookieName = "slash.access-token"
)
type ClaimsMessage struct {
Name string `json:"name"`
jwt.RegisteredClaims
}
// GenerateAccessToken generates an access token.
// username is the email of the user.
func GenerateAccessToken(username string, userID int32, expirationTime time.Time, secret string) (string, error) {
return generateToken(username, userID, AccessTokenAudienceName, expirationTime, []byte(secret))
}
// generateToken generates a jwt token.
func generateToken(username string, userID int32, audience string, expirationTime time.Time, secret []byte) (string, error) {
registeredClaims := jwt.RegisteredClaims{
Issuer: Issuer,
Audience: jwt.ClaimStrings{audience},
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: fmt.Sprint(userID),
}
if expirationTime.After(time.Now()) {
registeredClaims.ExpiresAt = jwt.NewNumericDate(expirationTime)
}
// Declare the token with the HS256 algorithm used for signing, and the claims.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &ClaimsMessage{
Name: username,
RegisteredClaims: registeredClaims,
})
token.Header["kid"] = KeyID
// Create the JWT string.
tokenString, err := token.SignedString(secret)
if err != nil {
return "", err
}
return tokenString, nil
}

View File

@ -1,11 +1,11 @@
package v1
type ActivityShorcutCreatePayload struct {
ShortcutID int `json:"shortcutId"`
ShortcutID int32 `json:"shortcutId"`
}
type ActivityShorcutViewPayload struct {
ShortcutID int `json:"shortcutId"`
ShortcutID int32 `json:"shortcutId"`
IP string `json:"ip"`
Referer string `json:"referer"`
UserAgent string `json:"userAgent"`

128
api/v1/analytics.go Normal file
View File

@ -0,0 +1,128 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/boojack/slash/store"
"github.com/labstack/echo/v4"
"github.com/mssola/useragent"
"golang.org/x/exp/slices"
)
type ReferenceInfo struct {
Name string `json:"name"`
Count int `json:"count"`
}
type DeviceInfo struct {
Name string `json:"name"`
Count int `json:"count"`
}
type BrowserInfo struct {
Name string `json:"name"`
Count int `json:"count"`
}
type AnalysisData struct {
ReferenceData []ReferenceInfo `json:"referenceData"`
DeviceData []DeviceInfo `json:"deviceData"`
BrowserData []BrowserInfo `json:"browserData"`
}
func (s *APIV1Service) registerAnalyticsRoutes(g *echo.Group) {
g.GET("/shortcut/:shortcutId/analytics", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("shortcut id is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
}
activities, err := s.Store.ListActivities(ctx, &store.FindActivity{
Type: store.ActivityShortcutView,
Where: []string{fmt.Sprintf("json_extract(payload, '$.shortcutId') = %d", shortcutID)},
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to get activities, err: %s", err)).SetInternal(err)
}
referenceMap := make(map[string]int)
deviceMap := make(map[string]int)
browserMap := make(map[string]int)
for _, activity := range activities {
payload := &ActivityShorcutViewPayload{}
if err := json.Unmarshal([]byte(activity.Payload), payload); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to unmarshal payload, err: %s", err)).SetInternal(err)
}
if _, ok := referenceMap[payload.Referer]; !ok {
referenceMap[payload.Referer] = 0
}
referenceMap[payload.Referer]++
ua := useragent.New(payload.UserAgent)
deviceName := ua.OSInfo().Name
browserName, _ := ua.Browser()
if _, ok := deviceMap[deviceName]; !ok {
deviceMap[deviceName] = 0
}
deviceMap[deviceName]++
if _, ok := browserMap[browserName]; !ok {
browserMap[browserName] = 0
}
browserMap[browserName]++
}
return c.JSON(http.StatusOK, &AnalysisData{
ReferenceData: mapToReferenceInfoSlice(referenceMap),
DeviceData: mapToDeviceInfoSlice(deviceMap),
BrowserData: mapToBrowserInfoSlice(browserMap),
})
})
}
func mapToReferenceInfoSlice(m map[string]int) []ReferenceInfo {
referenceInfoSlice := make([]ReferenceInfo, 0)
for key, value := range m {
referenceInfoSlice = append(referenceInfoSlice, ReferenceInfo{
Name: key,
Count: value,
})
}
slices.SortFunc(referenceInfoSlice, func(i, j ReferenceInfo) bool {
return i.Count > j.Count
})
return referenceInfoSlice
}
func mapToDeviceInfoSlice(m map[string]int) []DeviceInfo {
deviceInfoSlice := make([]DeviceInfo, 0)
for key, value := range m {
deviceInfoSlice = append(deviceInfoSlice, DeviceInfo{
Name: key,
Count: value,
})
}
slices.SortFunc(deviceInfoSlice, func(i, j DeviceInfo) bool {
return i.Count > j.Count
})
return deviceInfoSlice
}
func mapToBrowserInfoSlice(m map[string]int) []BrowserInfo {
browserInfoSlice := make([]BrowserInfo, 0)
for key, value := range m {
browserInfoSlice = append(browserInfoSlice, BrowserInfo{
Name: key,
Count: value,
})
}
slices.SortFunc(browserInfoSlice, func(i, j BrowserInfo) bool {
return i.Count > j.Count
})
return browserInfoSlice
}

View File

@ -1,14 +1,17 @@
package v1
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/boojack/shortify/server/auth"
"github.com/boojack/shortify/store"
"github.com/boojack/slash/api/auth"
storepb "github.com/boojack/slash/proto/gen/store"
"github.com/boojack/slash/store"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
)
@ -48,9 +51,16 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
return echo.NewHTTPError(http.StatusUnauthorized, "unmatched email and password")
}
if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
accessToken, err := auth.GenerateAccessToken(user.Email, user.ID, time.Now().Add(auth.AccessTokenDuration), secret)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to generate tokens, err: %s", err)).SetInternal(err)
}
if err := s.UpsertAccessTokenToStore(ctx, user, accessToken); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert access token, err: %s", err)).SetInternal(err)
}
cookieExp := time.Now().Add(auth.CookieExpDuration)
setTokenCookie(c, auth.AccessTokenCookieName, accessToken, cookieExp)
return c.JSON(http.StatusOK, convertUserFromStore(user))
})
@ -97,16 +107,65 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create user, err: %s", err)).SetInternal(err)
}
if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
accessToken, err := auth.GenerateAccessToken(user.Email, user.ID, time.Now().Add(auth.AccessTokenDuration), secret)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to generate tokens, err: %s", err)).SetInternal(err)
}
if err := s.UpsertAccessTokenToStore(ctx, user, accessToken); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert access token, err: %s", err)).SetInternal(err)
}
cookieExp := time.Now().Add(auth.CookieExpDuration)
setTokenCookie(c, auth.AccessTokenCookieName, accessToken, cookieExp)
return c.JSON(http.StatusOK, convertUserFromStore(user))
})
g.POST("/auth/logout", func(c echo.Context) error {
auth.RemoveTokensAndCookies(c)
RemoveTokensAndCookies(c)
c.Response().WriteHeader(http.StatusOK)
return nil
})
}
func (s *APIV1Service) UpsertAccessTokenToStore(ctx context.Context, user *store.User, accessToken string) error {
userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, user.ID)
if err != nil {
return errors.Wrap(err, "failed to get user access tokens")
}
userAccessToken := storepb.AccessTokensUserSetting_AccessToken{
AccessToken: accessToken,
Description: "Account sign in",
}
userAccessTokens = append(userAccessTokens, &userAccessToken)
if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
Value: &storepb.UserSetting_AccessTokensUserSetting{
AccessTokensUserSetting: &storepb.AccessTokensUserSetting{
AccessTokens: userAccessTokens,
},
},
}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert user setting, err: %s", err)).SetInternal(err)
}
return nil
}
// RemoveTokensAndCookies removes the jwt token from the cookies.
func RemoveTokensAndCookies(c echo.Context) {
cookieExp := time.Now().Add(-1 * time.Hour)
setTokenCookie(c, auth.AccessTokenCookieName, "", cookieExp)
}
// setTokenCookie sets the token to the cookie.
func setTokenCookie(c echo.Context, name, token string, expiration time.Time) {
cookie := new(http.Cookie)
cookie.Name = name
cookie.Value = token
cookie.Expires = expiration
cookie.Path = "/"
// Http-only helps mitigate the risk of client side script accessing the protected cookie.
cookie.HttpOnly = true
cookie.SameSite = http.SameSiteStrictMode
c.SetCookie(cookie)
}

View File

@ -3,36 +3,23 @@ package v1
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/boojack/shortify/internal/util"
"github.com/boojack/shortify/server/auth"
"github.com/boojack/shortify/store"
"github.com/boojack/slash/api/auth"
"github.com/boojack/slash/internal/util"
storepb "github.com/boojack/slash/proto/gen/store"
"github.com/boojack/slash/store"
"github.com/golang-jwt/jwt/v4"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
)
const (
// Context section
// The key name used to store user id in the context
// user id is extracted from the jwt token subject field.
userIDContextKey = "user-id"
UserIDContextKey = "user-id"
)
func getUserIDContextKey() string {
return userIDContextKey
}
// Claims creates a struct that will be encoded to a JWT.
// We add jwt.RegisteredClaims as an embedded type, to provide fields such as name.
type Claims struct {
Name string `json:"name"`
jwt.RegisteredClaims
}
func extractTokenFromHeader(c echo.Context) (string, error) {
authHeader := c.Request().Header.Get("Authorization")
if authHeader == "" {
@ -70,28 +57,28 @@ func audienceContains(audience jwt.ClaimStrings, token string) bool {
}
// JWTMiddleware validates the access token.
// If the access token is about to expire or has expired and the request has a valid refresh token, it
// will try to generate new access token and refresh token.
func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) echo.HandlerFunc {
func JWTMiddleware(s *APIV1Service, next echo.HandlerFunc, secret string) echo.HandlerFunc {
return func(c echo.Context) error {
path := c.Path()
ctx := c.Request().Context()
path := c.Request().URL.Path
method := c.Request().Method
if defaultAuthSkipper(c) {
// Pass auth and profile endpoints.
if util.HasPrefixes(path, "/api/v1/auth", "/api/v1/workspace/profile") {
return next(c)
}
token := findAccessToken(c)
if token == "" {
// When the request is not authenticated, we allow the user to access the shortcut endpoints for those public shortcuts.
if util.HasPrefixes(path, "/api/v1/workspace/profile", "/s/*") && method == http.MethodGet {
if util.HasPrefixes(path, "/s/*") && method == http.MethodGet {
return next(c)
}
return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
}
claims := &Claims{}
accessToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
claims := &auth.ClaimsMessage{}
_, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
}
@ -102,32 +89,29 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e
}
return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
})
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token"))
}
if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
}
generateToken := time.Until(claims.ExpiresAt.Time) < auth.RefreshThresholdDuration
// We either have a valid access token or we will attempt to generate new access token.
userID, err := util.ConvertStringToInt32(claims.Subject)
if err != nil {
var ve *jwt.ValidationError
if errors.As(err, &ve) {
// If expiration error is the only error, we will clear the err
// and generate new access token and refresh token
if ve.Errors == jwt.ValidationErrorExpired {
generateToken = true
}
} else {
return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token"))
}
return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.").WithInternal(err)
}
// We either have a valid access token or we will attempt to generate new access token and refresh token
ctx := c.Request().Context()
userID, err := strconv.Atoi(claims.Subject)
accessTokens, err := s.Store.GetUserAccessTokens(ctx, userID)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.")
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get user access tokens.").WithInternal(err)
}
if !validateAccessToken(token, accessTokens) {
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
}
// Even if there is no error, we still need to make sure the user still exists.
user, err := server.Store.GetUser(ctx, &store.FindUser{
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
@ -137,66 +121,17 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
}
if generateToken {
generateTokenFunc := func() error {
rc, err := c.Cookie(auth.RefreshTokenCookieName)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Missing refresh token.")
}
// Parses token and checks if it's valid.
refreshTokenClaims := &Claims{}
refreshToken, err := jwt.ParseWithClaims(rc.Value, refreshTokenClaims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, errors.Errorf("unexpected refresh token signing method=%v, expected %v", t.Header["alg"], jwt.SigningMethodHS256)
}
if kid, ok := t.Header["kid"].(string); ok {
if kid == "v1" {
return []byte(secret), nil
}
}
return nil, errors.Errorf("unexpected refresh token kid=%v", t.Header["kid"])
})
if err != nil {
if err == jwt.ErrSignatureInvalid {
return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Invalid refresh token signature.")
}
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
}
if !audienceContains(refreshTokenClaims.Audience, auth.RefreshTokenAudienceName) {
return echo.NewHTTPError(http.StatusUnauthorized,
fmt.Sprintf("Invalid refresh token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
refreshTokenClaims.Audience,
auth.RefreshTokenAudienceName,
))
}
// If we have a valid refresh token, we will generate new access token and refresh token
if refreshToken != nil && refreshToken.Valid {
if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
}
}
return nil
}
// It may happen that we still have a valid access token, but we encounter issue when trying to generate new token
// In such case, we won't return the error.
if err := generateTokenFunc(); err != nil && !accessToken.Valid {
return err
}
}
// Stores userID into context.
c.Set(getUserIDContextKey(), userID)
c.Set(UserIDContextKey, userID)
return next(c)
}
}
func defaultAuthSkipper(c echo.Context) bool {
path := c.Path()
return util.HasPrefixes(path, "/api/v1/auth")
func validateAccessToken(accessTokenString string, userAccessTokens []*storepb.AccessTokensUserSetting_AccessToken) bool {
for _, userAccessToken := range userAccessTokens {
if accessTokenString == userAccessToken.AccessToken {
return true
}
}
return false
}

View File

@ -3,10 +3,13 @@ package v1
import (
"encoding/json"
"fmt"
"html"
"net/http"
"net/url"
"strings"
"github.com/boojack/shortify/store"
storepb "github.com/boojack/slash/proto/gen/store"
"github.com/boojack/slash/store"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
)
@ -28,12 +31,12 @@ func (s *APIV1Service) registerRedirectorRoutes(g *echo.Group) {
if shortcut == nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found shortcut with name: %s", shortcutName))
}
if shortcut.Visibility != store.VisibilityPublic {
userID, ok := c.Get(getUserIDContextKey()).(int)
if shortcut.Visibility != storepb.Visibility_PUBLIC {
userID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
if shortcut.Visibility == store.VisibilityPrivate && shortcut.CreatorID != userID {
if shortcut.Visibility == storepb.Visibility_PRIVATE && shortcut.CreatorId != userID {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
}
@ -42,16 +45,49 @@ func (s *APIV1Service) registerRedirectorRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create activity, err: %s", err)).SetInternal(err)
}
if isValidURLString(shortcut.Link) {
return c.Redirect(http.StatusSeeOther, shortcut.Link)
}
return c.String(http.StatusOK, shortcut.Link)
return redirectToShortcut(c, shortcut)
})
}
func (s *APIV1Service) createShortcutViewActivity(c echo.Context, shortcut *store.Shortcut) error {
func redirectToShortcut(c echo.Context, shortcut *storepb.Shortcut) error {
isValidURL := isValidURLString(shortcut.Link)
if shortcut.OgMetadata == nil || (shortcut.OgMetadata.Title == "" && shortcut.OgMetadata.Description == "" && shortcut.OgMetadata.Image == "") {
if isValidURL {
return c.Redirect(http.StatusSeeOther, shortcut.Link)
}
return c.String(http.StatusOK, shortcut.Link)
}
htmlTemplate := `<html><head>%s</head><body>%s</body></html>`
metadataList := []string{
fmt.Sprintf(`<title>%s</title>`, shortcut.OgMetadata.Title),
fmt.Sprintf(`<meta name="description" content="%s" />`, shortcut.OgMetadata.Description),
fmt.Sprintf(`<meta property="og:title" content="%s" />`, shortcut.OgMetadata.Title),
fmt.Sprintf(`<meta property="og:description" content="%s" />`, shortcut.OgMetadata.Description),
fmt.Sprintf(`<meta property="og:image" content="%s" />`, shortcut.OgMetadata.Image),
`<meta property="og:type" content="website" />`,
// Twitter related metadata.
fmt.Sprintf(`<meta name="twitter:title" content="%s" />`, shortcut.OgMetadata.Title),
fmt.Sprintf(`<meta name="twitter:description" content="%s" />`, shortcut.OgMetadata.Description),
fmt.Sprintf(`<meta name="twitter:image" content="%s" />`, shortcut.OgMetadata.Image),
`<meta name="twitter:card" content="summary_large_image" />`,
}
if isValidURL {
metadataList = append(metadataList, fmt.Sprintf(`<meta property="og:url" content="%s" />`, shortcut.Link))
}
body := ""
if isValidURL {
body = fmt.Sprintf(`<script>window.location.href = "%s";</script>`, shortcut.Link)
} else {
body = html.EscapeString(shortcut.Link)
}
htmlString := fmt.Sprintf(htmlTemplate, strings.Join(metadataList, ""), body)
return c.HTML(http.StatusOK, htmlString)
}
func (s *APIV1Service) createShortcutViewActivity(c echo.Context, shortcut *storepb.Shortcut) error {
payload := &ActivityShorcutViewPayload{
ShortcutID: shortcut.ID,
ShortcutID: shortcut.Id,
IP: c.RealIP(),
Referer: c.Request().Referer(),
UserAgent: c.Request().UserAgent(),

View File

@ -5,13 +5,13 @@ import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/boojack/shortify/store"
"github.com/pkg/errors"
"github.com/boojack/slash/internal/util"
storepb "github.com/boojack/slash/proto/gen/store"
"github.com/boojack/slash/store"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
)
// Visibility is the type of a shortcut visibility.
@ -30,11 +30,17 @@ func (v Visibility) String() string {
return string(v)
}
type OpenGraphMetadata struct {
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
}
type Shortcut struct {
ID int `json:"id"`
ID int32 `json:"id"`
// Standard fields
CreatorID int `json:"creatorId"`
CreatorID int32 `json:"creatorId"`
Creator *User `json:"creator"`
CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
@ -43,33 +49,39 @@ type Shortcut struct {
// Domain specific fields
Name string `json:"name"`
Link string `json:"link"`
Title string `json:"title"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
Tags []string `json:"tags"`
View int `json:"view"`
OpenGraphMetadata *OpenGraphMetadata `json:"openGraphMetadata"`
}
type CreateShortcutRequest struct {
Name string `json:"name"`
Link string `json:"link"`
Title string `json:"title"`
Description string `json:"description"`
Visibility Visibility `json:"visibility"`
Tags []string `json:"tags"`
OpenGraphMetadata *OpenGraphMetadata `json:"openGraphMetadata"`
}
type PatchShortcutRequest struct {
RowStatus *RowStatus `json:"rowStatus"`
Name *string `json:"name"`
Link *string `json:"link"`
Title *string `json:"title"`
Description *string `json:"description"`
Visibility *Visibility `json:"visibility"`
Tags []string `json:"tags"`
OpenGraphMetadata *OpenGraphMetadata `json:"openGraphMetadata"`
}
func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
g.POST("/shortcut", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
userID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "missing user in session")
}
@ -78,13 +90,19 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("malformatted post shortcut request, err: %s", err)).SetInternal(err)
}
shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{
CreatorID: userID,
shortcut, err := s.Store.CreateShortcut(ctx, &storepb.Shortcut{
CreatorId: userID,
Name: strings.ToLower(create.Name),
Link: create.Link,
Title: create.Title,
Description: create.Description,
Visibility: convertVisibilityToStore(create.Visibility),
Tag: strings.Join(create.Tags, " "),
Visibility: convertVisibilityToStorepb(create.Visibility),
Tags: create.Tags,
OgMetadata: &storepb.OpenGraphMetadata{
Title: create.OpenGraphMetadata.Title,
Description: create.OpenGraphMetadata.Description,
Image: create.OpenGraphMetadata.Image,
},
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create shortcut, err: %s", err)).SetInternal(err)
@ -94,7 +112,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create shortcut activity, err: %s", err)).SetInternal(err)
}
shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStore(shortcut))
shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStorepb(shortcut))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to compose shortcut, err: %s", err)).SetInternal(err)
}
@ -103,11 +121,11 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
g.PATCH("/shortcut/:shortcutId", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
shortcutID, err := util.ConvertStringToInt32(c.Param("shortcutId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("shortcut ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
}
userID, ok := c.Get(getUserIDContextKey()).(int)
userID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "missing user in session")
}
@ -127,7 +145,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
if shortcut == nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found shortcut with id: %d", shortcutID))
}
if shortcut.CreatorID != userID && currentUser.Role != store.RoleAdmin {
if shortcut.CreatorId != userID && currentUser.Role != store.RoleAdmin {
return echo.NewHTTPError(http.StatusForbidden, "unauthorized to update shortcut")
}
@ -144,6 +162,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
ID: shortcutID,
Name: patch.Name,
Link: patch.Link,
Title: patch.Title,
Description: patch.Description,
}
if patch.RowStatus != nil {
@ -156,12 +175,19 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
tag := strings.Join(patch.Tags, " ")
shortcutUpdate.Tag = &tag
}
if patch.OpenGraphMetadata != nil {
shortcutUpdate.OpenGraphMetadata = &store.OpenGraphMetadata{
Title: patch.OpenGraphMetadata.Title,
Description: patch.OpenGraphMetadata.Description,
Image: patch.OpenGraphMetadata.Image,
}
}
shortcut, err = s.Store.UpdateShortcut(ctx, shortcutUpdate)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to patch shortcut, err: %s", err)).SetInternal(err)
}
shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStore(shortcut))
shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStorepb(shortcut))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to compose shortcut, err: %s", err)).SetInternal(err)
}
@ -170,24 +196,17 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
g.GET("/shortcut", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
userID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "missing user in session")
}
find := &store.FindShortcut{}
if creatorIDStr := c.QueryParam("creatorId"); creatorIDStr != "" {
creatorID, err := strconv.Atoi(creatorIDStr)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("unwanted creator id string: %s", creatorIDStr))
}
find.CreatorID = &creatorID
}
if tag := c.QueryParam("tag"); tag != "" {
find.Tag = &tag
}
list := []*store.Shortcut{}
list := []*storepb.Shortcut{}
find.VisibilityList = []store.Visibility{store.VisibilityWorkspace, store.VisibilityPublic}
visibleShortcutList, err := s.Store.ListShortcuts(ctx, find)
if err != nil {
@ -205,7 +224,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
shortcutMessageList := []*Shortcut{}
for _, shortcut := range list {
shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStore(shortcut))
shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStorepb(shortcut))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to compose shortcut, err: %s", err)).SetInternal(err)
}
@ -216,7 +235,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
g.GET("/shortcut/:id", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, err := strconv.Atoi(c.Param("id"))
shortcutID, err := util.ConvertStringToInt32(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("shortcut id is not a number: %s", c.Param("id"))).SetInternal(err)
}
@ -231,7 +250,7 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found shortcut with id: %d", shortcutID))
}
shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStore(shortcut))
shortcutMessage, err := s.composeShortcut(ctx, convertShortcutFromStorepb(shortcut))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to compose shortcut, err: %s", err)).SetInternal(err)
}
@ -240,11 +259,11 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
g.DELETE("/shortcut/:id", func(c echo.Context) error {
ctx := c.Request().Context()
shortcutID, err := strconv.Atoi(c.Param("id"))
shortcutID, err := util.ConvertStringToInt32(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("shortcut id is not a number: %s", c.Param("id"))).SetInternal(err)
}
userID, ok := c.Get(getUserIDContextKey()).(int)
userID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "missing user in session")
}
@ -264,41 +283,18 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
if shortcut == nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("not found shortcut with id: %d", shortcutID))
}
if shortcut.CreatorID != userID && currentUser.Role != store.RoleAdmin {
if shortcut.CreatorId != userID && currentUser.Role != store.RoleAdmin {
return echo.NewHTTPError(http.StatusForbidden, "Unauthorized to delete shortcut")
}
if err := s.Store.DeleteShortcut(ctx, &store.DeleteShortcut{
ID: shortcutID,
}); err != nil {
err = s.Store.DeleteShortcut(ctx, &store.DeleteShortcut{ID: shortcutID})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete shortcut, err: %s", err)).SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
}
func (s *APIV1Service) createShortcutCreateActivity(ctx context.Context, shortcut *store.Shortcut) error {
payload := &ActivityShorcutCreatePayload{
ShortcutID: shortcut.ID,
}
payloadStr, err := json.Marshal(payload)
if err != nil {
return errors.Wrap(err, "Failed to marshal activity payload")
}
activity := &store.Activity{
CreatorID: shortcut.CreatorID,
Type: store.ActivityShortcutCreate,
Level: store.ActivityInfo,
Payload: string(payloadStr),
}
_, err = s.Store.CreateActivity(ctx, activity)
if err != nil {
return errors.Wrap(err, "Failed to create activity")
}
return nil
}
func (s *APIV1Service) composeShortcut(ctx context.Context, shortcut *Shortcut) (*Shortcut, error) {
if shortcut == nil {
return nil, nil
@ -310,6 +306,9 @@ func (s *APIV1Service) composeShortcut(ctx context.Context, shortcut *Shortcut)
if err != nil {
return nil, errors.Wrap(err, "Failed to get creator")
}
if user == nil {
return nil, errors.New("Creator not found")
}
shortcut.Creator = convertUserFromStore(user)
activityList, err := s.Store.ListActivities(ctx, &store.FindActivity{
@ -325,35 +324,55 @@ func (s *APIV1Service) composeShortcut(ctx context.Context, shortcut *Shortcut)
return shortcut, nil
}
func convertVisibilityToStore(visibility Visibility) store.Visibility {
switch visibility {
case VisibilityPrivate:
return store.VisibilityPrivate
case VisibilityWorkspace:
return store.VisibilityWorkspace
case VisibilityPublic:
return store.VisibilityPublic
default:
return store.VisibilityPrivate
}
}
func convertShortcutFromStore(shortcut *store.Shortcut) *Shortcut {
tags := []string{}
if shortcut.Tag != "" {
tags = append(tags, strings.Split(shortcut.Tag, " ")...)
}
func convertShortcutFromStorepb(shortcut *storepb.Shortcut) *Shortcut {
return &Shortcut{
ID: shortcut.ID,
ID: shortcut.Id,
CreatedTs: shortcut.CreatedTs,
UpdatedTs: shortcut.UpdatedTs,
CreatorID: shortcut.CreatorID,
CreatorID: shortcut.CreatorId,
RowStatus: RowStatus(shortcut.RowStatus.String()),
Name: shortcut.Name,
Link: shortcut.Link,
Title: shortcut.Title,
Description: shortcut.Description,
Visibility: Visibility(shortcut.Visibility),
RowStatus: RowStatus(shortcut.RowStatus),
Tags: tags,
Visibility: Visibility(shortcut.Visibility.String()),
Tags: shortcut.Tags,
OpenGraphMetadata: &OpenGraphMetadata{
Title: shortcut.OgMetadata.Title,
Description: shortcut.OgMetadata.Description,
Image: shortcut.OgMetadata.Image,
},
}
}
func convertVisibilityToStorepb(visibility Visibility) storepb.Visibility {
switch visibility {
case VisibilityPublic:
return storepb.Visibility_PUBLIC
case VisibilityPrivate:
return storepb.Visibility_PRIVATE
default:
return storepb.Visibility_PUBLIC
}
}
func (s *APIV1Service) createShortcutCreateActivity(ctx context.Context, shortcut *storepb.Shortcut) error {
payload := &ActivityShorcutCreatePayload{
ShortcutID: shortcut.Id,
}
payloadStr, err := json.Marshal(payload)
if err != nil {
return errors.Wrap(err, "Failed to marshal activity payload")
}
activity := &store.Activity{
CreatorID: shortcut.CreatorId,
Type: store.ActivityShortcutCreate,
Level: store.ActivityInfo,
Payload: string(payloadStr),
}
_, err = s.Store.CreateActivity(ctx, activity)
if err != nil {
return errors.Wrap(err, "Failed to create activity")
}
return nil
}

View File

@ -9,6 +9,7 @@ import (
)
func (*APIV1Service) registerURLUtilRoutes(g *echo.Group) {
// GET /url/favicon?url=...
g.GET("/url/favicon", func(c echo.Context) error {
url := c.QueryParam("url")
icons, err := favicon.Find(url)

View File

@ -5,10 +5,9 @@ import (
"fmt"
"net/http"
"net/mail"
"strconv"
"github.com/boojack/shortify/store"
"github.com/boojack/slash/internal/util"
"github.com/boojack/slash/store"
"github.com/labstack/echo/v4"
"golang.org/x/crypto/bcrypt"
)
@ -39,7 +38,7 @@ func (r Role) String() string {
}
type User struct {
ID int `json:"id"`
ID int32 `json:"id"`
// Standard fields
CreatedTs int64 `json:"createdTs"`
@ -56,7 +55,7 @@ type CreateUserRequest struct {
Email string `json:"email"`
Nickname string `json:"nickname"`
Password string `json:"password"`
Role Role `json:"-"`
Role Role `json:"role"`
}
func (create CreateUserRequest) Validate() error {
@ -78,13 +77,56 @@ type PatchUserRequest struct {
Email *string `json:"email"`
Nickname *string `json:"nickname"`
Password *string `json:"password"`
}
type UserDelete struct {
ID int
Role *Role `json:"role"`
}
func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
g.POST("/user", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
}
currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by id").SetInternal(err)
}
if currentUser == nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
}
if currentUser.Role != store.RoleAdmin {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized to create user")
}
userCreate := &CreateUserRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(userCreate); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user request").SetInternal(err)
}
if err := userCreate.Validate(); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(userCreate.Password), bcrypt.DefaultCost)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
}
user, err := s.Store.CreateUser(ctx, &store.User{
Role: store.Role(userCreate.Role),
Email: userCreate.Email,
Nickname: userCreate.Nickname,
PasswordHash: string(passwordHash),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
}
userMessage := convertUserFromStore(user)
return c.JSON(http.StatusOK, userMessage)
})
g.GET("/user", func(c echo.Context) error {
ctx := c.Request().Context()
list, err := s.Store.ListUsers(ctx, &store.FindUser{})
@ -102,7 +144,7 @@ func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
// GET /api/user/me is used to check if the user is logged in.
g.GET("/user/me", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
userID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "missing auth session")
}
@ -119,7 +161,7 @@ func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
g.GET("/user/:id", func(c echo.Context) error {
ctx := c.Request().Context()
userID, err := strconv.Atoi(c.Param("id"))
userID, err := util.ConvertStringToInt32(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("user id is not a number: %s", c.Param("id"))).SetInternal(err)
}
@ -136,15 +178,24 @@ func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
g.PATCH("/user/:id", func(c echo.Context) error {
ctx := c.Request().Context()
userID, err := strconv.Atoi(c.Param("id"))
userID, err := util.ConvertStringToInt32(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("user id is not a number: %s", c.Param("id"))).SetInternal(err)
}
currentUserID, ok := c.Get(getUserIDContextKey()).(int)
currentUserID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "missing user in session")
}
if currentUserID != userID {
currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &currentUserID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "failed to find current user").SetInternal(err)
}
if currentUser == nil {
return echo.NewHTTPError(http.StatusUnauthorized, "missing user in session")
}
if currentUser.ID != userID && currentUser.Role != store.RoleAdmin {
return echo.NewHTTPError(http.StatusForbidden, "access forbidden for current session user").SetInternal(err)
}
@ -154,13 +205,12 @@ func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
}
updateUser := &store.UpdateUser{
ID: currentUserID,
ID: userID,
}
if userPatch.Email != nil {
if !validateEmail(*userPatch.Email) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid email format: %s", *userPatch.Email))
}
updateUser.Email = userPatch.Email
}
if userPatch.Nickname != nil {
@ -175,6 +225,24 @@ func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
passwordHashStr := string(passwordHash)
updateUser.PasswordHash = &passwordHashStr
}
if userPatch.RowStatus != nil {
rowStatus := store.RowStatus(*userPatch.RowStatus)
updateUser.RowStatus = &rowStatus
}
if userPatch.Role != nil {
adminRole := store.RoleAdmin
adminUsers, err := s.Store.ListUsers(ctx, &store.FindUser{
Role: &adminRole,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to list admin users, err: %s", err)).SetInternal(err)
}
if len(adminUsers) == 1 && adminUsers[0].ID == userID && *userPatch.Role != RoleAdmin {
return echo.NewHTTPError(http.StatusBadRequest, "cannot remove admin role from the last admin user")
}
role := store.Role(*userPatch.Role)
updateUser.Role = &role
}
user, err := s.Store.UpdateUser(ctx, updateUser)
if err != nil {
@ -186,7 +254,7 @@ func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
g.DELETE("/user/:id", func(c echo.Context) error {
ctx := c.Request().Context()
currentUserID, ok := c.Get(getUserIDContextKey()).(int)
currentUserID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "missing user in session")
}
@ -203,10 +271,22 @@ func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusForbidden, "access forbidden for current session user").SetInternal(err)
}
userID, err := strconv.Atoi(c.Param("id"))
userID, err := util.ConvertStringToInt32(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("user id is not a number: %s", c.Param("id"))).SetInternal(err)
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user, err: %s", err)).SetInternal(err)
}
if user == nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("user not found with ID: %d", userID)).SetInternal(err)
}
if user.Role == store.RoleAdmin {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("cannot delete admin user with ID: %d", userID)).SetInternal(err)
}
if err := s.Store.DeleteUser(ctx, &store.DeleteUser{
ID: userID,

View File

@ -13,11 +13,8 @@ const (
)
// String returns the string format of UserSettingKey type.
func (key UserSettingKey) String() string {
if key == UserSettingLocaleKey {
return "locale"
}
return ""
func (k UserSettingKey) String() string {
return string(k)
}
var (
@ -27,7 +24,7 @@ var (
type UserSetting struct {
UserID int
Key UserSettingKey `json:"key"`
// Value is a JSON string with basic value
// Value is a JSON string with basic value.
Value string `json:"value"`
}

View File

@ -1,9 +1,8 @@
package v1
import (
"github.com/boojack/shortify/server/profile"
"github.com/boojack/shortify/store"
"github.com/boojack/slash/server/profile"
"github.com/boojack/slash/store"
"github.com/labstack/echo/v4"
)
@ -29,6 +28,7 @@ func (s *APIV1Service) Start(apiGroup *echo.Group, secret string) {
s.registerAuthRoutes(apiV1Group, secret)
s.registerUserRoutes(apiV1Group)
s.registerShortcutRoutes(apiV1Group)
s.registerAnalyticsRoutes(apiV1Group)
redirectorGroup := apiGroup.Group("/s")
redirectorGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {

View File

@ -5,8 +5,8 @@ import (
"fmt"
"net/http"
"github.com/boojack/shortify/server/profile"
"github.com/boojack/shortify/store"
"github.com/boojack/slash/server/profile"
"github.com/boojack/slash/store"
"github.com/labstack/echo/v4"
)
@ -62,7 +62,7 @@ func (s *APIV1Service) registerWorkspaceRoutes(g *echo.Group) {
g.POST("/workspace/setting", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
userID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "missing user in session")
}
@ -97,7 +97,7 @@ func (s *APIV1Service) registerWorkspaceRoutes(g *echo.Group) {
g.GET("/workspace/setting", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
userID, ok := c.Get(UserIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "missing user in session")
}

171
api/v2/acl.go Normal file
View File

@ -0,0 +1,171 @@
package v2
import (
"context"
"net/http"
"strings"
"github.com/boojack/slash/api/auth"
"github.com/boojack/slash/internal/util"
storepb "github.com/boojack/slash/proto/gen/store"
"github.com/boojack/slash/store"
"github.com/golang-jwt/jwt/v4"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
var authenticationAllowlistMethods = map[string]bool{}
// IsAuthenticationAllowed returns whether the method is exempted from authentication.
func IsAuthenticationAllowed(fullMethodName string) bool {
if strings.HasPrefix(fullMethodName, "/grpc.reflection") {
return true
}
return authenticationAllowlistMethods[fullMethodName]
}
// ContextKey is the key type of context value.
type ContextKey int
const (
// The key name used to store user id in the context
// user id is extracted from the jwt token subject field.
UserIDContextKey ContextKey = iota
)
// GRPCAuthInterceptor is the auth interceptor for gRPC server.
type GRPCAuthInterceptor struct {
Store *store.Store
secret string
}
// NewGRPCAuthInterceptor returns a new API auth interceptor.
func NewGRPCAuthInterceptor(store *store.Store, secret string) *GRPCAuthInterceptor {
return &GRPCAuthInterceptor{
Store: store,
secret: secret,
}
}
// AuthenticationInterceptor is the unary interceptor for gRPC API.
func (in *GRPCAuthInterceptor) AuthenticationInterceptor(ctx context.Context, request any, serverInfo *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "failed to parse metadata from incoming context")
}
accessToken, err := getTokenFromMetadata(md)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "failed to get access token from metadata: %v", err)
}
userID, err := in.authenticate(ctx, accessToken)
if err != nil {
if IsAuthenticationAllowed(serverInfo.FullMethod) {
return handler(ctx, request)
}
return nil, err
}
userAccessTokens, err := in.Store.GetUserAccessTokens(ctx, userID)
if err != nil {
return nil, errors.Wrap(err, "failed to get user access tokens")
}
if !validateAccessToken(accessToken, userAccessTokens) {
return nil, status.Errorf(codes.Unauthenticated, "invalid access token")
}
// Stores userID into context.
childCtx := context.WithValue(ctx, UserIDContextKey, userID)
return handler(childCtx, request)
}
func (in *GRPCAuthInterceptor) authenticate(ctx context.Context, accessTokenStr string) (int32, error) {
if accessTokenStr == "" {
return 0, status.Errorf(codes.Unauthenticated, "access token not found")
}
claims := &auth.ClaimsMessage{}
_, err := jwt.ParseWithClaims(accessTokenStr, claims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, status.Errorf(codes.Unauthenticated, "unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
}
if kid, ok := t.Header["kid"].(string); ok {
if kid == "v1" {
return []byte(in.secret), nil
}
}
return nil, status.Errorf(codes.Unauthenticated, "unexpected access token kid=%v", t.Header["kid"])
})
if err != nil {
return 0, status.Errorf(codes.Unauthenticated, "Invalid or expired access token")
}
if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
return 0, status.Errorf(codes.Unauthenticated,
"invalid access token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
claims.Audience,
auth.AccessTokenAudienceName,
)
}
userID, err := util.ConvertStringToInt32(claims.Subject)
if err != nil {
return 0, status.Errorf(codes.Unauthenticated, "malformed ID %q in the access token", claims.Subject)
}
user, err := in.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return 0, status.Errorf(codes.Unauthenticated, "failed to find user ID %q in the access token", userID)
}
if user == nil {
return 0, status.Errorf(codes.Unauthenticated, "user ID %q not exists in the access token", userID)
}
if user.RowStatus == store.Archived {
return 0, status.Errorf(codes.Unauthenticated, "user ID %q has been deactivated by administrators", userID)
}
return userID, nil
}
func getTokenFromMetadata(md metadata.MD) (string, error) {
// Try to get the token from the authorization header first.
authorizationHeaders := md.Get("Authorization")
if len(authorizationHeaders) > 0 {
authHeaderParts := strings.Fields(authorizationHeaders[0])
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
return "", errors.Errorf("authorization header format must be Bearer {token}")
}
return authHeaderParts[1], nil
}
// Try to get the token from the cookie header.
var accessToken string
for _, t := range append(md.Get("grpcgateway-cookie"), md.Get("cookie")...) {
header := http.Header{}
header.Add("Cookie", t)
request := http.Request{Header: header}
if v, _ := request.Cookie(auth.AccessTokenCookieName); v != nil {
accessToken = v.Value
}
}
return accessToken, nil
}
func audienceContains(audience jwt.ClaimStrings, token string) bool {
for _, v := range audience {
if v == token {
return true
}
}
return false
}
func validateAccessToken(accessTokenString string, userAccessTokens []*storepb.AccessTokensUserSetting_AccessToken) bool {
for _, userAccessToken := range userAccessTokens {
if accessTokenString == userAccessToken.AccessToken {
return true
}
}
return false
}

17
api/v2/common.go Normal file
View File

@ -0,0 +1,17 @@
package v2
import (
apiv2pb "github.com/boojack/slash/proto/gen/api/v2"
"github.com/boojack/slash/store"
)
func convertRowStatusFromStore(rowStatus store.RowStatus) apiv2pb.RowStatus {
switch rowStatus {
case store.Normal:
return apiv2pb.RowStatus_NORMAL
case store.Archived:
return apiv2pb.RowStatus_ARCHIVED
default:
return apiv2pb.RowStatus_ROW_STATUS_UNSPECIFIED
}
}

View File

@ -0,0 +1,97 @@
package v2
import (
"context"
apiv2pb "github.com/boojack/slash/proto/gen/api/v2"
storepb "github.com/boojack/slash/proto/gen/store"
"github.com/boojack/slash/store"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ShortcutService struct {
apiv2pb.UnimplementedShortcutServiceServer
Secret string
Store *store.Store
}
// NewShortcutService creates a new Shortcut service.
func NewShortcutService(secret string, store *store.Store) *ShortcutService {
return &ShortcutService{
Secret: secret,
Store: store,
}
}
func (s *ShortcutService) ListShortcuts(ctx context.Context, _ *apiv2pb.ListShortcutsRequest) (*apiv2pb.ListShortcutsResponse, error) {
userID := ctx.Value(UserIDContextKey).(int32)
find := &store.FindShortcut{}
find.VisibilityList = []store.Visibility{store.VisibilityWorkspace, store.VisibilityPublic}
visibleShortcutList, err := s.Store.ListShortcuts(ctx, find)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to fetch visible shortcut list, err: %v", err)
}
find.VisibilityList = []store.Visibility{store.VisibilityPrivate}
find.CreatorID = &userID
shortcutList, err := s.Store.ListShortcuts(ctx, find)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to fetch private shortcut list, err: %v", err)
}
shortcutList = append(shortcutList, visibleShortcutList...)
shortcuts := []*apiv2pb.Shortcut{}
for _, shortcut := range shortcutList {
shortcuts = append(shortcuts, convertShortcutFromStorepb(shortcut))
}
response := &apiv2pb.ListShortcutsResponse{
Shortcuts: shortcuts,
}
return response, nil
}
func (s *ShortcutService) GetShortcut(ctx context.Context, request *apiv2pb.GetShortcutRequest) (*apiv2pb.GetShortcutResponse, error) {
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
Name: &request.Name,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get shortcut by name: %v", err)
}
if shortcut == nil {
return nil, status.Errorf(codes.NotFound, "shortcut not found")
}
userID := ctx.Value(UserIDContextKey).(int32)
if shortcut.Visibility == storepb.Visibility_PRIVATE && shortcut.CreatorId != userID {
return nil, status.Errorf(codes.PermissionDenied, "Permission denied")
}
shortcutMessage := convertShortcutFromStorepb(shortcut)
response := &apiv2pb.GetShortcutResponse{
Shortcut: shortcutMessage,
}
return response, nil
}
func convertShortcutFromStorepb(shortcut *storepb.Shortcut) *apiv2pb.Shortcut {
return &apiv2pb.Shortcut{
Id: shortcut.Id,
CreatorId: shortcut.CreatorId,
CreatedTs: shortcut.CreatedTs,
UpdatedTs: shortcut.UpdatedTs,
RowStatus: apiv2pb.RowStatus(shortcut.RowStatus),
Name: shortcut.Name,
Link: shortcut.Link,
Title: shortcut.Title,
Tags: shortcut.Tags,
Description: shortcut.Description,
Visibility: apiv2pb.Visibility(shortcut.Visibility),
OgMetadata: &apiv2pb.OpenGraphMetadata{
Title: shortcut.OgMetadata.Title,
Description: shortcut.OgMetadata.Description,
Image: shortcut.OgMetadata.Image,
},
}
}

235
api/v2/user_service.go Normal file
View File

@ -0,0 +1,235 @@
package v2
import (
"context"
"github.com/boojack/slash/api/auth"
apiv2pb "github.com/boojack/slash/proto/gen/api/v2"
storepb "github.com/boojack/slash/proto/gen/store"
"github.com/boojack/slash/store"
"github.com/golang-jwt/jwt/v4"
"github.com/pkg/errors"
"golang.org/x/exp/slices"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
type UserService struct {
apiv2pb.UnimplementedUserServiceServer
Secret string
Store *store.Store
}
// NewUserService creates a new UserService.
func NewUserService(secret string, store *store.Store) *UserService {
return &UserService{
Secret: secret,
Store: store,
}
}
func (s *UserService) GetUser(ctx context.Context, request *apiv2pb.GetUserRequest) (*apiv2pb.GetUserResponse, error) {
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &request.Id,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to find user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
userMessage := convertUserFromStore(user)
response := &apiv2pb.GetUserResponse{
User: userMessage,
}
return response, nil
}
func (s *UserService) ListUserAccessTokens(ctx context.Context, request *apiv2pb.ListUserAccessTokensRequest) (*apiv2pb.ListUserAccessTokensResponse, error) {
userID := ctx.Value(UserIDContextKey).(int32)
if userID != request.Id {
return nil, status.Errorf(codes.PermissionDenied, "Permission denied")
}
userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, userID)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list access tokens: %v", err)
}
accessTokens := []*apiv2pb.UserAccessToken{}
for _, userAccessToken := range userAccessTokens {
claims := &auth.ClaimsMessage{}
_, err := jwt.ParseWithClaims(userAccessToken.AccessToken, claims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
}
if kid, ok := t.Header["kid"].(string); ok {
if kid == "v1" {
return []byte(s.Secret), nil
}
}
return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
})
if err != nil {
// If the access token is invalid or expired, just ignore it.
continue
}
userAccessToken := &apiv2pb.UserAccessToken{
AccessToken: userAccessToken.AccessToken,
Description: userAccessToken.Description,
IssuedAt: timestamppb.New(claims.IssuedAt.Time),
}
if claims.ExpiresAt != nil {
userAccessToken.ExpiresAt = timestamppb.New(claims.ExpiresAt.Time)
}
accessTokens = append(accessTokens, userAccessToken)
}
// Sort by issued time in descending order.
slices.SortFunc(accessTokens, func(i, j *apiv2pb.UserAccessToken) bool {
return i.IssuedAt.Seconds > j.IssuedAt.Seconds
})
response := &apiv2pb.ListUserAccessTokensResponse{
AccessTokens: accessTokens,
}
return response, nil
}
func (s *UserService) CreateUserAccessToken(ctx context.Context, request *apiv2pb.CreateUserAccessTokenRequest) (*apiv2pb.CreateUserAccessTokenResponse, error) {
userID := ctx.Value(UserIDContextKey).(int32)
if userID != request.Id {
return nil, status.Errorf(codes.PermissionDenied, "Permission denied")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
accessToken, err := auth.GenerateAccessToken(user.Email, user.ID, request.UserAccessToken.ExpiresAt.AsTime(), s.Secret)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to generate access token: %v", err)
}
claims := &auth.ClaimsMessage{}
_, err = jwt.ParseWithClaims(accessToken, claims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
}
if kid, ok := t.Header["kid"].(string); ok {
if kid == "v1" {
return []byte(s.Secret), nil
}
}
return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to parse access token: %v", err)
}
// Upsert the access token to user setting store.
if err := s.UpsertAccessTokenToStore(ctx, user, accessToken, request.UserAccessToken.Description); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert access token to store: %v", err)
}
userAccessToken := &apiv2pb.UserAccessToken{
AccessToken: accessToken,
Description: request.UserAccessToken.Description,
IssuedAt: timestamppb.New(claims.IssuedAt.Time),
}
if claims.ExpiresAt != nil {
userAccessToken.ExpiresAt = timestamppb.New(claims.ExpiresAt.Time)
}
response := &apiv2pb.CreateUserAccessTokenResponse{
AccessToken: userAccessToken,
}
return response, nil
}
func (s *UserService) DeleteUserAccessToken(ctx context.Context, request *apiv2pb.DeleteUserAccessTokenRequest) (*apiv2pb.DeleteUserAccessTokenResponse, error) {
userID := ctx.Value(UserIDContextKey).(int32)
if userID != request.Id {
return nil, status.Errorf(codes.PermissionDenied, "Permission denied")
}
userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, userID)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list access tokens: %v", err)
}
updatedUserAccessTokens := []*storepb.AccessTokensUserSetting_AccessToken{}
for _, userAccessToken := range userAccessTokens {
if userAccessToken.AccessToken == request.AccessToken {
continue
}
updatedUserAccessTokens = append(updatedUserAccessTokens, userAccessToken)
}
if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{
UserId: userID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
Value: &storepb.UserSetting_AccessTokensUserSetting{
AccessTokensUserSetting: &storepb.AccessTokensUserSetting{
AccessTokens: updatedUserAccessTokens,
},
},
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
}
return &apiv2pb.DeleteUserAccessTokenResponse{}, nil
}
func (s *UserService) UpsertAccessTokenToStore(ctx context.Context, user *store.User, accessToken, description string) error {
userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, user.ID)
if err != nil {
return errors.Wrap(err, "failed to get user access tokens")
}
userAccessToken := storepb.AccessTokensUserSetting_AccessToken{
AccessToken: accessToken,
Description: description,
}
userAccessTokens = append(userAccessTokens, &userAccessToken)
if _, err := s.Store.UpsertUserSetting(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
Value: &storepb.UserSetting_AccessTokensUserSetting{
AccessTokensUserSetting: &storepb.AccessTokensUserSetting{
AccessTokens: userAccessTokens,
},
},
}); err != nil {
return errors.Wrap(err, "failed to upsert user setting")
}
return nil
}
func convertUserFromStore(user *store.User) *apiv2pb.User {
return &apiv2pb.User{
Id: int32(user.ID),
RowStatus: convertRowStatusFromStore(user.RowStatus),
CreatedTs: user.CreatedTs,
UpdatedTs: user.UpdatedTs,
Role: convertUserRoleFromStore(user.Role),
Email: user.Email,
Nickname: user.Nickname,
}
}
func convertUserRoleFromStore(role store.Role) apiv2pb.Role {
switch role {
case store.RoleAdmin:
return apiv2pb.Role_ADMIN
case store.RoleUser:
return apiv2pb.Role_USER
default:
return apiv2pb.Role_ROLE_UNSPECIFIED
}
}

71
api/v2/v2.go Normal file
View File

@ -0,0 +1,71 @@
package v2
import (
"context"
"fmt"
apiv2pb "github.com/boojack/slash/proto/gen/api/v2"
"github.com/boojack/slash/server/profile"
"github.com/boojack/slash/store"
grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/labstack/echo/v4"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type APIV2Service struct {
Secret string
Profile *profile.Profile
Store *store.Store
grpcServer *grpc.Server
grpcServerPort int
}
func NewAPIV2Service(secret string, profile *profile.Profile, store *store.Store, grpcServerPort int) *APIV2Service {
authProvider := NewGRPCAuthInterceptor(store, secret)
grpcServer := grpc.NewServer(
grpc.ChainUnaryInterceptor(
authProvider.AuthenticationInterceptor,
),
)
apiv2pb.RegisterUserServiceServer(grpcServer, NewUserService(secret, store))
apiv2pb.RegisterShortcutServiceServer(grpcServer, NewShortcutService(secret, store))
return &APIV2Service{
Secret: secret,
Profile: profile,
Store: store,
grpcServer: grpcServer,
grpcServerPort: grpcServerPort,
}
}
func (s *APIV2Service) GetGRPCServer() *grpc.Server {
return s.grpcServer
}
// RegisterGateway registers the gRPC-Gateway with the given Echo instance.
func (s *APIV2Service) RegisterGateway(ctx context.Context, e *echo.Echo) error {
// Create a client connection to the gRPC Server we just started.
// This is where the gRPC-Gateway proxies the requests.
conn, err := grpc.DialContext(
ctx,
fmt.Sprintf(":%d", s.grpcServerPort),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return err
}
gwMux := grpcRuntime.NewServeMux()
if err := apiv2pb.RegisterUserServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterShortcutServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
e.Any("/api/v2/*", echo.WrapHandler(gwMux))
return nil
}

View File

@ -12,21 +12,14 @@ import (
"github.com/spf13/viper"
_ "modernc.org/sqlite"
"github.com/boojack/shortify/server"
_profile "github.com/boojack/shortify/server/profile"
"github.com/boojack/shortify/store"
"github.com/boojack/shortify/store/db"
"github.com/boojack/slash/server"
_profile "github.com/boojack/slash/server/profile"
"github.com/boojack/slash/store"
"github.com/boojack/slash/store/db"
)
const (
greetingBanner = `
`
greetingBanner = `Welcome to Slash!`
)
var (
@ -36,8 +29,8 @@ var (
data string
rootCmd = &cobra.Command{
Use: "shortify",
Short: "",
Use: "slash",
Short: `An open source, self-hosted bookmarks and link sharing platform.`,
Run: func(_cmd *cobra.Command, _args []string) {
ctx, cancel := context.WithCancel(context.Background())
db := db.NewDB(profile)
@ -89,7 +82,7 @@ func Execute() error {
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVarP(&mode, "mode", "m", "dev", `mode of server, can be "prod" or "dev"`)
rootCmd.PersistentFlags().StringVarP(&mode, "mode", "m", "demo", `mode of server, can be "prod" or "dev" or "demo"`)
rootCmd.PersistentFlags().IntVarP(&port, "port", "p", 8082, "port of server")
rootCmd.PersistentFlags().StringVarP(&data, "data", "d", "", "data directory")
@ -106,9 +99,9 @@ func init() {
panic(err)
}
viper.SetDefault("mode", "dev")
viper.SetDefault("mode", "demo")
viper.SetDefault("port", 8082)
viper.SetEnvPrefix("shortify")
viper.SetEnvPrefix("slash")
}
func initConfig() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,47 @@
# The Browser Extension of Slash
Slash provides a browser extension to help you use your shortcuts in the search bar to go to the corresponding URL.
## How to use
### Generate an access token
1. Go to your Slash instance and sign in with your account.
2. Go to the settings page and click on the "Create" button to create an access token.
![](./assets/extension-usage/create-access-token.png)
3. Copy the access token and save it somewhere safe.
![](./assets/extension-usage/copy-access-token.png)
### Install the extension
> **Note**: The extension is not published to the Chrome Web Store yet. You can install it from the source code.
For Chromuim based browsers, you can download the packed extension from the [resources](https://github.com/boojack/slash/tree/main/extension/resources).
For Firefox, we don't support the Firefox Add-ons platform yet. And we are working on it.
### Configure the extension
1. Click on the extension icon and click on the "Settings" button.
![](./assets/extension-usage/extension-setting-button.png)
2. Enter your Slash's domain and paste the access token you generated in the previous step.
![](./assets/extension-usage/extension-setting-page.png)
3. Click on the "Save" button to save the settings.
4. Click on the extension icon again, you will see a list of your shortcuts.
![](./assets/extension-usage/extension-screenshot.png)
### Use your shortcuts in the search bar
You can use your shortcuts in the search bar of your browser. For example, if you have a shortcut named `gh` for [GitHub](https://github.com), you can type `s/gh` in the search bar and press `Enter` to go to [GitHub](https://github.com).
![](./assets/extension-usage/shortcut-url.png)

39
docs/install.md Normal file
View File

@ -0,0 +1,39 @@
# Self-hosting Slash with Docker
Slash is designed for self-hosting through Docker. No Docker expertise is required to launch your own instance. Just basic understanding of command line and networking.
## Requirements
The only requirement is a server with Docker installed.
## Docker Run
To deploy Slash using docker run, just one command is needed:
```bash
docker run -d --name slash --publish 5231:5231 --volume ~/.slash/:/var/opt/slash yourselfhosted/slash:latest
```
This will start Slash in the background and expose it on port `5231`. Data is stored in `~/.slash/`. You can customize the port and data directory.
## Upgrade
To upgrade Slash to latest version, stop and remove the old container first:
```bash
docker stop slash && docker rm slash
```
It's recommended but optional to backup database:
```bash
cp -r ~/.slash/slash_prod.db ~/.slash/slash_prod.db.bak
```
Then pull the latest image:
```bash
docker pull yourselfhosted/slash:latest
```
Finally, restart Slash by following the steps in [Docker Run](#docker-run).

33
extension/.eslintrc.json Normal file
View File

@ -0,0 +1,33 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint", "prettier"],
"ignorePatterns": ["node_modules", "dist", "public"],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
],
"@typescript-eslint/no-explicit-any": ["off"],
"react/react-in-jsx-scope": "off",
"react/jsx-no-target-blank": "off"
},
"settings": {
"react": {
"version": "detect"
}
}
}

38
extension/.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
#cache
.turbo
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*
out/
build/
dist/
.plasmo
# bpp - http://bpp.browser.market/
keys.json
# typescript
.tsbuildinfo

8
extension/.prettierrc.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
printWidth: 140,
useTabs: false,
semi: true,
singleQuote: false,
plugins: [require.resolve("@trivago/prettier-plugin-sort-imports")],
importOrder: ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^@/((?!less).+)", "^[./]", "^(.+).css"],
};

1
extension/README.md Normal file
View File

@ -0,0 +1 @@
# Slash Browser Extension

BIN
extension/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

60
extension/package.json Normal file
View File

@ -0,0 +1,60 @@
{
"name": "slash-extexnsion",
"displayName": "Slash",
"version": "0.1.0",
"description": "An open source, self-hosted bookmarks and link sharing platform. Save and share your links very easily.",
"author": "steven",
"scripts": {
"dev": "plasmo dev",
"build": "plasmo build",
"package": "plasmo package",
"lint": "eslint --ext .js,.ts,.tsx, src",
"lint-fix": "eslint --ext .js,.ts,.tsx, src --fix"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/joy": "5.0.0-beta.0",
"@plasmohq/storage": "^1.7.2",
"axios": "^1.4.0",
"classnames": "^2.3.2",
"lodash-es": "^4.17.21",
"lucide-react": "^0.264.0",
"plasmo": "0.82.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hot-toast": "^2.4.1",
"zustand": "^4.4.1"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "4.1.0",
"@types/chrome": "0.0.241",
"@types/lodash-es": "^4.17.8",
"@types/node": "20.4.2",
"@types/react": "18.2.15",
"@types/react-dom": "18.2.7",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.27.1",
"postcss": "^8.4.27",
"prettier": "2.6.2",
"tailwindcss": "^3.3.3",
"typescript": "5.1.6"
},
"manifest": {
"host_permissions": [
"http://*/*",
"https://*/*"
],
"permissions": [
"tabs",
"activeTab",
"scripting",
"storage"
]
}
}

7107
extension/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
/* eslint-disable no-undef */
/**
* @type {import('postcss').ProcessOptions}
*/
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

View File

@ -0,0 +1,53 @@
import type { Shortcut } from "@/types/proto/api/v2/shortcut_service_pb";
import { Storage } from "@plasmohq/storage";
const storage = new Storage();
const urlRegex = /https?:\/\/s\/(.+)/;
chrome.tabs.onUpdated.addListener(async (tabId, _, tab) => {
if (!tab.url) {
return;
}
const shortcutName = getShortcutNameFromUrl(tab.url);
if (shortcutName) {
const shortcuts = (await storage.getItem<Shortcut[]>("shortcuts")) || [];
const shortcut = shortcuts.find((shortcut) => shortcut.name === shortcutName);
if (!shortcut) {
return;
}
return chrome.tabs.update(tabId, { url: shortcut.link });
}
});
const getShortcutNameFromUrl = (urlString: string) => {
const matchResult = urlRegex.exec(urlString);
if (matchResult === null) {
return getShortcutNameFromSearchUrl(urlString);
}
return matchResult[1];
};
const getShortcutNameFromSearchUrl = (urlString: string) => {
const url = new URL(urlString);
if ((url.hostname === "www.google.com" || url.hostname === "www.bing.com") && url.pathname === "/search") {
const params = new URLSearchParams(url.search);
const shortcutName = params.get("q");
if (typeof shortcutName === "string" && shortcutName.startsWith("s/")) {
return shortcutName.slice(2);
}
} else if (url.hostname === "www.baidu.com" && url.pathname === "/s") {
const params = new URLSearchParams(url.search);
const shortcutName = params.get("wd");
if (typeof shortcutName === "string" && shortcutName.startsWith("s/")) {
return shortcutName.slice(2);
}
} else if (url.hostname === "duckduckgo.com" && url.pathname === "/") {
const params = new URLSearchParams(url.search);
const shortcutName = params.get("q");
if (typeof shortcutName === "string" && shortcutName.startsWith("s/")) {
return shortcutName.slice(2);
}
}
return "";
};

View File

@ -0,0 +1,3 @@
import * as Icon from "lucide-react";
export default Icon;

View File

@ -0,0 +1,49 @@
import { Button } from "@mui/joy";
import { useStorage } from "@plasmohq/storage/hook";
import axios from "axios";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { ListShortcutsResponse } from "@/types/proto/api/v2/shortcut_service_pb";
import "../style.css";
import Icon from "./Icon";
const PullShortcutsButton = () => {
const [domain] = useStorage("domain");
const [accessToken] = useStorage("access_token");
const [, setShortcuts] = useStorage("shortcuts");
const [isPulling, setIsPulling] = useState(false);
useEffect(() => {
if (domain && accessToken) {
handlePullShortcuts(true);
}
}, [domain, accessToken]);
const handlePullShortcuts = async (silence = false) => {
try {
setIsPulling(true);
const {
data: { shortcuts },
} = await axios.get<ListShortcutsResponse>(`${domain}/api/v2/shortcuts`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
setShortcuts(shortcuts);
if (!silence) {
toast.success("Shortcuts pulled");
}
} catch (error) {
toast.error("Failed to pull shortcuts, error: " + error.message);
}
setIsPulling(false);
};
return (
<Button loading={isPulling} color="neutral" variant="plain" size="sm" onClick={() => handlePullShortcuts()}>
<Icon.RefreshCcw className="w-4 h-auto" />
</Button>
);
};
export default PullShortcutsButton;

View File

@ -0,0 +1,77 @@
import type { Shortcut } from "@/types/proto/api/v2/shortcut_service_pb";
import { useStorage } from "@plasmohq/storage/hook";
import classNames from "classnames";
import { useEffect, useState } from "react";
import useFaviconStore from "../stores/favicon";
import Icon from "./Icon";
interface Props {
shortcut: Shortcut;
}
const ShortcutView = (props: Props) => {
const { shortcut } = props;
const faviconStore = useFaviconStore();
const [domain] = useStorage<string>("domain", "");
const [favicon, setFavicon] = useState<string | undefined>(undefined);
useEffect(() => {
faviconStore.getOrFetchUrlFavicon(shortcut.link).then((url) => {
if (url) {
setFavicon(url);
}
});
}, [shortcut.link]);
const handleShortcutLinkClick = () => {
const shortcutLink = `${domain}/s/${shortcut.name}`;
chrome.tabs.create({ url: shortcutLink });
};
return (
<>
<div
className={classNames(
"group w-full px-3 py-2 flex flex-col justify-start items-start border rounded-lg hover:bg-gray-100 hover:shadow"
)}
>
<div className="w-full flex flex-row justify-start items-center">
<span className={classNames("w-5 h-5 flex justify-center items-center overflow-clip shrink-0")}>
{favicon ? (
<img className="w-full h-auto rounded-full" src={favicon} decoding="async" loading="lazy" />
) : (
<Icon.CircleSlash className="w-full h-auto text-gray-400" />
)}
</span>
<div className="ml-1 w-[calc(100%-20px)] flex flex-col justify-start items-start">
<div className="w-full flex flex-row justify-start items-center">
<button
className={classNames(
"max-w-full flex flex-row px-1 mr-1 justify-start items-center cursor-pointer rounded-md hover:underline"
)}
onClick={handleShortcutLinkClick}
>
<div className="truncate">
<span>{shortcut.title}</span>
{shortcut.title ? (
<span className="text-gray-400">(s/{shortcut.name})</span>
) : (
<>
<span className="text-gray-400">s/</span>
<span className="truncate">{shortcut.name}</span>
</>
)}
</div>
<span className="hidden group-hover:block ml-1 cursor-pointer shrink-0">
<Icon.ExternalLink className="w-4 h-auto text-gray-600" />
</span>
</button>
</div>
</div>
</div>
</div>
</>
);
};
export default ShortcutView;

View File

@ -0,0 +1,18 @@
import type { Shortcut } from "@/types/proto/api/v2/shortcut_service_pb";
import { useStorage } from "@plasmohq/storage/hook";
import classNames from "classnames";
import ShortcutView from "./ShortcutView";
const ShortcutsContainer = () => {
const [shortcuts] = useStorage<Shortcut[]>("shortcuts", (v) => (v ? v : []));
return (
<div className={classNames("w-full grid grid-cols-2 gap-2")}>
{shortcuts.map((shortcut) => {
return <ShortcutView key={shortcut.id} shortcut={shortcut} />;
})}
</div>
);
};
export default ShortcutsContainer;

View File

@ -0,0 +1,14 @@
import { Storage } from "@plasmohq/storage";
import axios from "axios";
const storage = new Storage();
export const getUrlFavicon = async (url: string) => {
const domain = await storage.getItem<string>("domain");
const accessToken = await storage.getItem<string>("access_token");
return axios.get<string>(`${domain}/api/v1/url/favicon?url=${url}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
};

View File

@ -0,0 +1,5 @@
import { isNull, isUndefined } from "lodash-es";
export const isNullorUndefined = (value: any) => {
return isNull(value) || isUndefined(value);
};

89
extension/src/options.tsx Normal file
View File

@ -0,0 +1,89 @@
import { Button, Input } from "@mui/joy";
import { useStorage } from "@plasmohq/storage/hook";
import { useEffect, useState } from "react";
import { Toaster, toast } from "react-hot-toast";
import Icon from "./components/Icon";
import "./style.css";
interface SettingState {
domain: string;
accessToken: string;
}
const IndexOptions = () => {
const [domain, setDomain] = useStorage<string>("domain", (v) => (v ? v : ""));
const [accessToken, setAccessToken] = useStorage<string>("access_token", (v) => (v ? v : ""));
const [settingState, setSettingState] = useState<SettingState>({
domain,
accessToken,
});
useEffect(() => {
setSettingState({
domain,
accessToken,
});
}, [domain, accessToken]);
const setPartialSettingState = (partialSettingState: Partial<SettingState>) => {
setSettingState((prevState) => ({
...prevState,
...partialSettingState,
}));
};
const handleSaveSetting = () => {
setDomain(settingState.domain);
setAccessToken(settingState.accessToken);
toast.success("Setting saved");
};
return (
<>
<div className="w-full">
<div className="w-full max-w-lg mx-auto flex flex-col justify-start items-start mt-12">
<h2 className="flex flex-row justify-start items-center mb-6 font-mono">
<Icon.CircleSlash className="w-8 h-auto mr-2 text-gray-500" />
<span className="text-lg">Slash</span>
<span className="mx-2 text-gray-400">/</span>
<span className="text-lg">Setting</span>
</h2>
<div className="w-full flex flex-col justify-start items-start mb-4">
<span className="mb-2 text-base">Domain</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder="The domain of your Slash instance"
value={settingState.domain}
onChange={(e) => setPartialSettingState({ domain: e.target.value })}
/>
</div>
</div>
<div className="w-full flex flex-col justify-start items-start">
<span className="mb-2 text-base">Access Token</span>
<div className="relative w-full">
<Input
className="w-full"
type="text"
placeholder="The access token of your Slash instance"
value={settingState.accessToken}
onChange={(e) => setPartialSettingState({ accessToken: e.target.value })}
/>
</div>
</div>
<div className="w-full mt-6">
<Button onClick={handleSaveSetting}>Save</Button>
</div>
</div>
</div>
<Toaster position="top-center" />
</>
);
};
export default IndexOptions;

78
extension/src/popup.tsx Normal file
View File

@ -0,0 +1,78 @@
import { Button } from "@mui/joy";
import { useStorage } from "@plasmohq/storage/hook";
import { Toaster } from "react-hot-toast";
import { Shortcut } from "@/types/proto/api/v2/shortcut_service_pb";
import Icon from "./components/Icon";
import PullShortcutsButton from "./components/PullShortcutsButton";
import ShortcutsContainer from "./components/ShortcutsContainer";
import "./style.css";
const IndexPopup = () => {
const [domain] = useStorage<string>("domain", "");
const [accessToken] = useStorage<string>("access_token", "");
const [shortcuts] = useStorage<Shortcut[]>("shortcuts", []);
const isInitialized = domain && accessToken;
const handleSettingButtonClick = () => {
chrome.runtime.openOptionsPage();
};
const handleRefreshButtonClick = () => {
chrome.runtime.reload();
};
return (
<>
<div className="w-full min-w-[480px] p-4">
<div className="w-full flex flex-row justify-between items-center text-sm">
<div className="flex flex-row justify-start items-center font-mono">
<Icon.CircleSlash className="w-5 h-auto mr-1 text-gray-500 -mt-0.5" />
<span className="font-mono">Slash</span>
{isInitialized && (
<>
<span className="mx-1 text-gray-400">/</span>
<span>Shortcuts</span>
<span className="mr-1 text-gray-500">({shortcuts.length})</span>
<PullShortcutsButton />
</>
)}
</div>
<div>
<Button size="sm" variant="plain" color="neutral" onClick={handleSettingButtonClick}>
<Icon.Settings className="w-5 h-auto" />
</Button>
</div>
</div>
<div className="w-full mt-4">
{isInitialized ? (
shortcuts.length !== 0 ? (
<ShortcutsContainer />
) : (
<div className="w-full flex flex-col justify-center items-center">
<p>No shortcut found.</p>
</div>
)
) : (
<div className="w-full flex flex-col justify-start items-center">
<p>No domain and access token found.</p>
<div className="w-full flex flex-row justify-center items-center py-4">
<Button size="sm" color="primary" onClick={handleSettingButtonClick}>
<Icon.Settings className="w-5 h-auto mr-1" /> Setting
</Button>
<span className="mx-2">Or</span>
<Button size="sm" variant="outlined" color="neutral" onClick={handleRefreshButtonClick}>
<Icon.RefreshCcw className="w-5 h-auto mr-1" /> Refresh
</Button>
</div>
</div>
)}
</div>
</div>
<Toaster position="top-right" />
</>
);
};
export default IndexPopup;

View File

@ -0,0 +1,41 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { getUrlFavicon } from "../helpers/api";
interface FaviconState {
cache: {
[key: string]: string;
};
getOrFetchUrlFavicon: (url: string) => Promise<string>;
}
const useFaviconStore = create<FaviconState>()(
persist(
(set, get) => ({
cache: {},
getOrFetchUrlFavicon: async (url: string) => {
const cache = get().cache;
if (cache[url]) {
return cache[url];
}
try {
const { data: favicon } = await getUrlFavicon(url);
if (favicon) {
cache[url] = favicon;
set(cache);
return favicon;
}
} catch (error) {
// do nothing
}
return "";
},
}),
{
name: "favicon_cache",
}
)
);
export default useFaviconStore;

25
extension/src/style.css Normal file
View File

@ -0,0 +1,25 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body,
html,
#root {
@apply text-base;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Noto Sans", "Noto Sans CJK SC", "Microsoft YaHei UI", "Microsoft YaHei",
"WenQuanYi Micro Hei", sans-serif, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
}
@layer utilities {
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}

View File

@ -0,0 +1,25 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file api/v2/common.proto (package slash.api.v2, syntax proto3)
/* eslint-disable */
// @ts-nocheck
/**
* @generated from enum slash.api.v2.RowStatus
*/
export declare enum RowStatus {
/**
* @generated from enum value: ROW_STATUS_UNSPECIFIED = 0;
*/
ROW_STATUS_UNSPECIFIED = 0,
/**
* @generated from enum value: NORMAL = 1;
*/
NORMAL = 1,
/**
* @generated from enum value: ARCHIVED = 2;
*/
ARCHIVED = 2,
}

View File

@ -0,0 +1,19 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file api/v2/common.proto (package slash.api.v2, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { proto3 } from "@bufbuild/protobuf";
/**
* @generated from enum slash.api.v2.RowStatus
*/
export const RowStatus = proto3.makeEnum(
"slash.api.v2.RowStatus",
[
{no: 0, name: "ROW_STATUS_UNSPECIFIED"},
{no: 1, name: "NORMAL"},
{no: 2, name: "ARCHIVED"},
],
);

View File

@ -0,0 +1,238 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file api/v2/shortcut_service.proto (package slash.api.v2, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
import { Message, proto3 } from "@bufbuild/protobuf";
import type { RowStatus } from "./common_pb.js";
/**
* @generated from enum slash.api.v2.Visibility
*/
export declare enum Visibility {
/**
* @generated from enum value: VISIBILITY_UNSPECIFIED = 0;
*/
VISIBILITY_UNSPECIFIED = 0,
/**
* @generated from enum value: PRIVATE = 1;
*/
PRIVATE = 1,
/**
* @generated from enum value: WORKSPACE = 2;
*/
WORKSPACE = 2,
/**
* @generated from enum value: PUBLIC = 3;
*/
PUBLIC = 3,
}
/**
* @generated from message slash.api.v2.Shortcut
*/
export declare class Shortcut extends Message<Shortcut> {
/**
* @generated from field: int32 id = 1;
*/
id: number;
/**
* @generated from field: int32 creator_id = 2;
*/
creatorId: number;
/**
* @generated from field: int64 created_ts = 3;
*/
createdTs: bigint;
/**
* @generated from field: int64 updated_ts = 4;
*/
updatedTs: bigint;
/**
* @generated from field: slash.api.v2.RowStatus row_status = 5;
*/
rowStatus: RowStatus;
/**
* @generated from field: string name = 6;
*/
name: string;
/**
* @generated from field: string link = 7;
*/
link: string;
/**
* @generated from field: string title = 8;
*/
title: string;
/**
* @generated from field: repeated string tags = 9;
*/
tags: string[];
/**
* @generated from field: string description = 10;
*/
description: string;
/**
* @generated from field: slash.api.v2.Visibility visibility = 11;
*/
visibility: Visibility;
/**
* @generated from field: slash.api.v2.OpenGraphMetadata og_metadata = 12;
*/
ogMetadata?: OpenGraphMetadata;
constructor(data?: PartialMessage<Shortcut>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.Shortcut";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Shortcut;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Shortcut;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Shortcut;
static equals(a: Shortcut | PlainMessage<Shortcut> | undefined, b: Shortcut | PlainMessage<Shortcut> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.OpenGraphMetadata
*/
export declare class OpenGraphMetadata extends Message<OpenGraphMetadata> {
/**
* @generated from field: string title = 1;
*/
title: string;
/**
* @generated from field: string description = 2;
*/
description: string;
/**
* @generated from field: string image = 3;
*/
image: string;
constructor(data?: PartialMessage<OpenGraphMetadata>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.OpenGraphMetadata";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): OpenGraphMetadata;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): OpenGraphMetadata;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): OpenGraphMetadata;
static equals(a: OpenGraphMetadata | PlainMessage<OpenGraphMetadata> | undefined, b: OpenGraphMetadata | PlainMessage<OpenGraphMetadata> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.ListShortcutsRequest
*/
export declare class ListShortcutsRequest extends Message<ListShortcutsRequest> {
constructor(data?: PartialMessage<ListShortcutsRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.ListShortcutsRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ListShortcutsRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ListShortcutsRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ListShortcutsRequest;
static equals(a: ListShortcutsRequest | PlainMessage<ListShortcutsRequest> | undefined, b: ListShortcutsRequest | PlainMessage<ListShortcutsRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.ListShortcutsResponse
*/
export declare class ListShortcutsResponse extends Message<ListShortcutsResponse> {
/**
* @generated from field: repeated slash.api.v2.Shortcut shortcuts = 1;
*/
shortcuts: Shortcut[];
constructor(data?: PartialMessage<ListShortcutsResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.ListShortcutsResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ListShortcutsResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ListShortcutsResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ListShortcutsResponse;
static equals(a: ListShortcutsResponse | PlainMessage<ListShortcutsResponse> | undefined, b: ListShortcutsResponse | PlainMessage<ListShortcutsResponse> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.GetShortcutRequest
*/
export declare class GetShortcutRequest extends Message<GetShortcutRequest> {
/**
* @generated from field: string name = 1;
*/
name: string;
constructor(data?: PartialMessage<GetShortcutRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.GetShortcutRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetShortcutRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetShortcutRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetShortcutRequest;
static equals(a: GetShortcutRequest | PlainMessage<GetShortcutRequest> | undefined, b: GetShortcutRequest | PlainMessage<GetShortcutRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.GetShortcutResponse
*/
export declare class GetShortcutResponse extends Message<GetShortcutResponse> {
/**
* @generated from field: slash.api.v2.Shortcut shortcut = 1;
*/
shortcut?: Shortcut;
constructor(data?: PartialMessage<GetShortcutResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.GetShortcutResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetShortcutResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetShortcutResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetShortcutResponse;
static equals(a: GetShortcutResponse | PlainMessage<GetShortcutResponse> | undefined, b: GetShortcutResponse | PlainMessage<GetShortcutResponse> | undefined): boolean;
}

View File

@ -0,0 +1,92 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file api/v2/shortcut_service.proto (package slash.api.v2, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { proto3 } from "@bufbuild/protobuf";
import { RowStatus } from "./common_pb.js";
/**
* @generated from enum slash.api.v2.Visibility
*/
export const Visibility = proto3.makeEnum(
"slash.api.v2.Visibility",
[
{no: 0, name: "VISIBILITY_UNSPECIFIED"},
{no: 1, name: "PRIVATE"},
{no: 2, name: "WORKSPACE"},
{no: 3, name: "PUBLIC"},
],
);
/**
* @generated from message slash.api.v2.Shortcut
*/
export const Shortcut = proto3.makeMessageType(
"slash.api.v2.Shortcut",
() => [
{ no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 2, name: "creator_id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 3, name: "created_ts", kind: "scalar", T: 3 /* ScalarType.INT64 */ },
{ no: 4, name: "updated_ts", kind: "scalar", T: 3 /* ScalarType.INT64 */ },
{ no: 5, name: "row_status", kind: "enum", T: proto3.getEnumType(RowStatus) },
{ no: 6, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 7, name: "link", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 8, name: "title", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 9, name: "tags", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
{ no: 10, name: "description", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 11, name: "visibility", kind: "enum", T: proto3.getEnumType(Visibility) },
{ no: 12, name: "og_metadata", kind: "message", T: OpenGraphMetadata },
],
);
/**
* @generated from message slash.api.v2.OpenGraphMetadata
*/
export const OpenGraphMetadata = proto3.makeMessageType(
"slash.api.v2.OpenGraphMetadata",
() => [
{ no: 1, name: "title", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "description", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 3, name: "image", kind: "scalar", T: 9 /* ScalarType.STRING */ },
],
);
/**
* @generated from message slash.api.v2.ListShortcutsRequest
*/
export const ListShortcutsRequest = proto3.makeMessageType(
"slash.api.v2.ListShortcutsRequest",
[],
);
/**
* @generated from message slash.api.v2.ListShortcutsResponse
*/
export const ListShortcutsResponse = proto3.makeMessageType(
"slash.api.v2.ListShortcutsResponse",
() => [
{ no: 1, name: "shortcuts", kind: "message", T: Shortcut, repeated: true },
],
);
/**
* @generated from message slash.api.v2.GetShortcutRequest
*/
export const GetShortcutRequest = proto3.makeMessageType(
"slash.api.v2.GetShortcutRequest",
() => [
{ no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
],
);
/**
* @generated from message slash.api.v2.GetShortcutResponse
*/
export const GetShortcutResponse = proto3.makeMessageType(
"slash.api.v2.GetShortcutResponse",
() => [
{ no: 1, name: "shortcut", kind: "message", T: Shortcut },
],
);

View File

@ -0,0 +1,327 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file api/v2/user_service.proto (package slash.api.v2, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage, Timestamp } from "@bufbuild/protobuf";
import { Message, proto3 } from "@bufbuild/protobuf";
import type { RowStatus } from "./common_pb.js";
/**
* @generated from enum slash.api.v2.Role
*/
export declare enum Role {
/**
* @generated from enum value: ROLE_UNSPECIFIED = 0;
*/
ROLE_UNSPECIFIED = 0,
/**
* @generated from enum value: ADMIN = 1;
*/
ADMIN = 1,
/**
* @generated from enum value: USER = 2;
*/
USER = 2,
}
/**
* @generated from message slash.api.v2.User
*/
export declare class User extends Message<User> {
/**
* @generated from field: int32 id = 1;
*/
id: number;
/**
* @generated from field: slash.api.v2.RowStatus row_status = 2;
*/
rowStatus: RowStatus;
/**
* @generated from field: int64 created_ts = 3;
*/
createdTs: bigint;
/**
* @generated from field: int64 updated_ts = 4;
*/
updatedTs: bigint;
/**
* @generated from field: slash.api.v2.Role role = 6;
*/
role: Role;
/**
* @generated from field: string email = 7;
*/
email: string;
/**
* @generated from field: string nickname = 8;
*/
nickname: string;
constructor(data?: PartialMessage<User>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.User";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): User;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): User;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): User;
static equals(a: User | PlainMessage<User> | undefined, b: User | PlainMessage<User> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.GetUserRequest
*/
export declare class GetUserRequest extends Message<GetUserRequest> {
/**
* @generated from field: int32 id = 1;
*/
id: number;
constructor(data?: PartialMessage<GetUserRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.GetUserRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetUserRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetUserRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetUserRequest;
static equals(a: GetUserRequest | PlainMessage<GetUserRequest> | undefined, b: GetUserRequest | PlainMessage<GetUserRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.GetUserResponse
*/
export declare class GetUserResponse extends Message<GetUserResponse> {
/**
* @generated from field: slash.api.v2.User user = 1;
*/
user?: User;
constructor(data?: PartialMessage<GetUserResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.GetUserResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetUserResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): GetUserResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): GetUserResponse;
static equals(a: GetUserResponse | PlainMessage<GetUserResponse> | undefined, b: GetUserResponse | PlainMessage<GetUserResponse> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.ListUserAccessTokensRequest
*/
export declare class ListUserAccessTokensRequest extends Message<ListUserAccessTokensRequest> {
/**
* id is the user id.
*
* @generated from field: int32 id = 1;
*/
id: number;
constructor(data?: PartialMessage<ListUserAccessTokensRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.ListUserAccessTokensRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ListUserAccessTokensRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ListUserAccessTokensRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ListUserAccessTokensRequest;
static equals(a: ListUserAccessTokensRequest | PlainMessage<ListUserAccessTokensRequest> | undefined, b: ListUserAccessTokensRequest | PlainMessage<ListUserAccessTokensRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.ListUserAccessTokensResponse
*/
export declare class ListUserAccessTokensResponse extends Message<ListUserAccessTokensResponse> {
/**
* @generated from field: repeated slash.api.v2.UserAccessToken access_tokens = 1;
*/
accessTokens: UserAccessToken[];
constructor(data?: PartialMessage<ListUserAccessTokensResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.ListUserAccessTokensResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ListUserAccessTokensResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ListUserAccessTokensResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ListUserAccessTokensResponse;
static equals(a: ListUserAccessTokensResponse | PlainMessage<ListUserAccessTokensResponse> | undefined, b: ListUserAccessTokensResponse | PlainMessage<ListUserAccessTokensResponse> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.CreateUserAccessTokenRequest
*/
export declare class CreateUserAccessTokenRequest extends Message<CreateUserAccessTokenRequest> {
/**
* id is the user id.
*
* @generated from field: int32 id = 1;
*/
id: number;
/**
* @generated from field: slash.api.v2.UserAccessToken user_access_token = 2;
*/
userAccessToken?: UserAccessToken;
constructor(data?: PartialMessage<CreateUserAccessTokenRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.CreateUserAccessTokenRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateUserAccessTokenRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CreateUserAccessTokenRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CreateUserAccessTokenRequest;
static equals(a: CreateUserAccessTokenRequest | PlainMessage<CreateUserAccessTokenRequest> | undefined, b: CreateUserAccessTokenRequest | PlainMessage<CreateUserAccessTokenRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.CreateUserAccessTokenResponse
*/
export declare class CreateUserAccessTokenResponse extends Message<CreateUserAccessTokenResponse> {
/**
* @generated from field: slash.api.v2.UserAccessToken access_token = 1;
*/
accessToken?: UserAccessToken;
constructor(data?: PartialMessage<CreateUserAccessTokenResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.CreateUserAccessTokenResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateUserAccessTokenResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CreateUserAccessTokenResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CreateUserAccessTokenResponse;
static equals(a: CreateUserAccessTokenResponse | PlainMessage<CreateUserAccessTokenResponse> | undefined, b: CreateUserAccessTokenResponse | PlainMessage<CreateUserAccessTokenResponse> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.DeleteUserAccessTokenRequest
*/
export declare class DeleteUserAccessTokenRequest extends Message<DeleteUserAccessTokenRequest> {
/**
* id is the user id.
*
* @generated from field: int32 id = 1;
*/
id: number;
/**
* access_token is the access token to delete.
*
* @generated from field: string access_token = 2;
*/
accessToken: string;
constructor(data?: PartialMessage<DeleteUserAccessTokenRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.DeleteUserAccessTokenRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DeleteUserAccessTokenRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DeleteUserAccessTokenRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DeleteUserAccessTokenRequest;
static equals(a: DeleteUserAccessTokenRequest | PlainMessage<DeleteUserAccessTokenRequest> | undefined, b: DeleteUserAccessTokenRequest | PlainMessage<DeleteUserAccessTokenRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.DeleteUserAccessTokenResponse
*/
export declare class DeleteUserAccessTokenResponse extends Message<DeleteUserAccessTokenResponse> {
constructor(data?: PartialMessage<DeleteUserAccessTokenResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.DeleteUserAccessTokenResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DeleteUserAccessTokenResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DeleteUserAccessTokenResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DeleteUserAccessTokenResponse;
static equals(a: DeleteUserAccessTokenResponse | PlainMessage<DeleteUserAccessTokenResponse> | undefined, b: DeleteUserAccessTokenResponse | PlainMessage<DeleteUserAccessTokenResponse> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.UserAccessToken
*/
export declare class UserAccessToken extends Message<UserAccessToken> {
/**
* @generated from field: string access_token = 1;
*/
accessToken: string;
/**
* @generated from field: string description = 2;
*/
description: string;
/**
* @generated from field: google.protobuf.Timestamp issued_at = 3;
*/
issuedAt?: Timestamp;
/**
* @generated from field: google.protobuf.Timestamp expires_at = 4;
*/
expiresAt?: Timestamp;
constructor(data?: PartialMessage<UserAccessToken>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.UserAccessToken";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): UserAccessToken;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): UserAccessToken;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): UserAccessToken;
static equals(a: UserAccessToken | PlainMessage<UserAccessToken> | undefined, b: UserAccessToken | PlainMessage<UserAccessToken> | undefined): boolean;
}

View File

@ -0,0 +1,129 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file api/v2/user_service.proto (package slash.api.v2, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { proto3, Timestamp } from "@bufbuild/protobuf";
import { RowStatus } from "./common_pb.js";
/**
* @generated from enum slash.api.v2.Role
*/
export const Role = proto3.makeEnum(
"slash.api.v2.Role",
[
{no: 0, name: "ROLE_UNSPECIFIED"},
{no: 1, name: "ADMIN"},
{no: 2, name: "USER"},
],
);
/**
* @generated from message slash.api.v2.User
*/
export const User = proto3.makeMessageType(
"slash.api.v2.User",
() => [
{ no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 2, name: "row_status", kind: "enum", T: proto3.getEnumType(RowStatus) },
{ no: 3, name: "created_ts", kind: "scalar", T: 3 /* ScalarType.INT64 */ },
{ no: 4, name: "updated_ts", kind: "scalar", T: 3 /* ScalarType.INT64 */ },
{ no: 6, name: "role", kind: "enum", T: proto3.getEnumType(Role) },
{ no: 7, name: "email", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 8, name: "nickname", kind: "scalar", T: 9 /* ScalarType.STRING */ },
],
);
/**
* @generated from message slash.api.v2.GetUserRequest
*/
export const GetUserRequest = proto3.makeMessageType(
"slash.api.v2.GetUserRequest",
() => [
{ no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
],
);
/**
* @generated from message slash.api.v2.GetUserResponse
*/
export const GetUserResponse = proto3.makeMessageType(
"slash.api.v2.GetUserResponse",
() => [
{ no: 1, name: "user", kind: "message", T: User },
],
);
/**
* @generated from message slash.api.v2.ListUserAccessTokensRequest
*/
export const ListUserAccessTokensRequest = proto3.makeMessageType(
"slash.api.v2.ListUserAccessTokensRequest",
() => [
{ no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
],
);
/**
* @generated from message slash.api.v2.ListUserAccessTokensResponse
*/
export const ListUserAccessTokensResponse = proto3.makeMessageType(
"slash.api.v2.ListUserAccessTokensResponse",
() => [
{ no: 1, name: "access_tokens", kind: "message", T: UserAccessToken, repeated: true },
],
);
/**
* @generated from message slash.api.v2.CreateUserAccessTokenRequest
*/
export const CreateUserAccessTokenRequest = proto3.makeMessageType(
"slash.api.v2.CreateUserAccessTokenRequest",
() => [
{ no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 2, name: "user_access_token", kind: "message", T: UserAccessToken },
],
);
/**
* @generated from message slash.api.v2.CreateUserAccessTokenResponse
*/
export const CreateUserAccessTokenResponse = proto3.makeMessageType(
"slash.api.v2.CreateUserAccessTokenResponse",
() => [
{ no: 1, name: "access_token", kind: "message", T: UserAccessToken },
],
);
/**
* @generated from message slash.api.v2.DeleteUserAccessTokenRequest
*/
export const DeleteUserAccessTokenRequest = proto3.makeMessageType(
"slash.api.v2.DeleteUserAccessTokenRequest",
() => [
{ no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 2, name: "access_token", kind: "scalar", T: 9 /* ScalarType.STRING */ },
],
);
/**
* @generated from message slash.api.v2.DeleteUserAccessTokenResponse
*/
export const DeleteUserAccessTokenResponse = proto3.makeMessageType(
"slash.api.v2.DeleteUserAccessTokenResponse",
[],
);
/**
* @generated from message slash.api.v2.UserAccessToken
*/
export const UserAccessToken = proto3.makeMessageType(
"slash.api.v2.UserAccessToken",
() => [
{ no: 1, name: "access_token", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "description", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 3, name: "issued_at", kind: "message", T: Timestamp },
{ no: 4, name: "expires_at", kind: "message", T: Timestamp },
],
);

View File

@ -0,0 +1,25 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file store/common.proto (package slash.store, syntax proto3)
/* eslint-disable */
// @ts-nocheck
/**
* @generated from enum slash.store.RowStatus
*/
export declare enum RowStatus {
/**
* @generated from enum value: ROW_STATUS_UNSPECIFIED = 0;
*/
ROW_STATUS_UNSPECIFIED = 0,
/**
* @generated from enum value: NORMAL = 1;
*/
NORMAL = 1,
/**
* @generated from enum value: ARCHIVED = 2;
*/
ARCHIVED = 2,
}

View File

@ -0,0 +1,19 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file store/common.proto (package slash.store, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { proto3 } from "@bufbuild/protobuf";
/**
* @generated from enum slash.store.RowStatus
*/
export const RowStatus = proto3.makeEnum(
"slash.store.RowStatus",
[
{no: 0, name: "ROW_STATUS_UNSPECIFIED"},
{no: 1, name: "NORMAL"},
{no: 2, name: "ARCHIVED"},
],
);

View File

@ -0,0 +1,147 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file store/shortcut.proto (package slash.store, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
import { Message, proto3 } from "@bufbuild/protobuf";
import type { RowStatus } from "./common_pb.js";
/**
* @generated from enum slash.store.Visibility
*/
export declare enum Visibility {
/**
* @generated from enum value: VISIBILITY_UNSPECIFIED = 0;
*/
VISIBILITY_UNSPECIFIED = 0,
/**
* @generated from enum value: PRIVATE = 1;
*/
PRIVATE = 1,
/**
* @generated from enum value: WORKSPACE = 2;
*/
WORKSPACE = 2,
/**
* @generated from enum value: PUBLIC = 3;
*/
PUBLIC = 3,
}
/**
* @generated from message slash.store.Shortcut
*/
export declare class Shortcut extends Message<Shortcut> {
/**
* @generated from field: int32 id = 1;
*/
id: number;
/**
* @generated from field: int32 creator_id = 2;
*/
creatorId: number;
/**
* @generated from field: int64 created_ts = 3;
*/
createdTs: bigint;
/**
* @generated from field: int64 updated_ts = 4;
*/
updatedTs: bigint;
/**
* @generated from field: slash.store.RowStatus row_status = 5;
*/
rowStatus: RowStatus;
/**
* @generated from field: string name = 6;
*/
name: string;
/**
* @generated from field: string link = 7;
*/
link: string;
/**
* @generated from field: string title = 8;
*/
title: string;
/**
* @generated from field: repeated string tags = 9;
*/
tags: string[];
/**
* @generated from field: string description = 10;
*/
description: string;
/**
* @generated from field: slash.store.Visibility visibility = 11;
*/
visibility: Visibility;
/**
* @generated from field: slash.store.OpenGraphMetadata og_metadata = 12;
*/
ogMetadata?: OpenGraphMetadata;
constructor(data?: PartialMessage<Shortcut>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.store.Shortcut";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Shortcut;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): Shortcut;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): Shortcut;
static equals(a: Shortcut | PlainMessage<Shortcut> | undefined, b: Shortcut | PlainMessage<Shortcut> | undefined): boolean;
}
/**
* @generated from message slash.store.OpenGraphMetadata
*/
export declare class OpenGraphMetadata extends Message<OpenGraphMetadata> {
/**
* @generated from field: string title = 1;
*/
title: string;
/**
* @generated from field: string description = 2;
*/
description: string;
/**
* @generated from field: string image = 3;
*/
image: string;
constructor(data?: PartialMessage<OpenGraphMetadata>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.store.OpenGraphMetadata";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): OpenGraphMetadata;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): OpenGraphMetadata;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): OpenGraphMetadata;
static equals(a: OpenGraphMetadata | PlainMessage<OpenGraphMetadata> | undefined, b: OpenGraphMetadata | PlainMessage<OpenGraphMetadata> | undefined): boolean;
}

View File

@ -0,0 +1,54 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file store/shortcut.proto (package slash.store, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { proto3 } from "@bufbuild/protobuf";
import { RowStatus } from "./common_pb.js";
/**
* @generated from enum slash.store.Visibility
*/
export const Visibility = proto3.makeEnum(
"slash.store.Visibility",
[
{no: 0, name: "VISIBILITY_UNSPECIFIED"},
{no: 1, name: "PRIVATE"},
{no: 2, name: "WORKSPACE"},
{no: 3, name: "PUBLIC"},
],
);
/**
* @generated from message slash.store.Shortcut
*/
export const Shortcut = proto3.makeMessageType(
"slash.store.Shortcut",
() => [
{ no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 2, name: "creator_id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 3, name: "created_ts", kind: "scalar", T: 3 /* ScalarType.INT64 */ },
{ no: 4, name: "updated_ts", kind: "scalar", T: 3 /* ScalarType.INT64 */ },
{ no: 5, name: "row_status", kind: "enum", T: proto3.getEnumType(RowStatus) },
{ no: 6, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 7, name: "link", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 8, name: "title", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 9, name: "tags", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
{ no: 10, name: "description", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 11, name: "visibility", kind: "enum", T: proto3.getEnumType(Visibility) },
{ no: 12, name: "og_metadata", kind: "message", T: OpenGraphMetadata },
],
);
/**
* @generated from message slash.store.OpenGraphMetadata
*/
export const OpenGraphMetadata = proto3.makeMessageType(
"slash.store.OpenGraphMetadata",
() => [
{ no: 1, name: "title", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "description", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 3, name: "image", kind: "scalar", T: 9 /* ScalarType.STRING */ },
],
);

View File

@ -0,0 +1,116 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file store/user_setting.proto (package slash.store, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
import { Message, proto3 } from "@bufbuild/protobuf";
/**
* @generated from enum slash.store.UserSettingKey
*/
export declare enum UserSettingKey {
/**
* @generated from enum value: USER_SETTING_KEY_UNSPECIFIED = 0;
*/
USER_SETTING_KEY_UNSPECIFIED = 0,
/**
* @generated from enum value: USER_SETTING_ACCESS_TOKENS = 1;
*/
USER_SETTING_ACCESS_TOKENS = 1,
}
/**
* @generated from message slash.store.UserSetting
*/
export declare class UserSetting extends Message<UserSetting> {
/**
* @generated from field: int32 user_id = 1;
*/
userId: number;
/**
* @generated from field: slash.store.UserSettingKey key = 2;
*/
key: UserSettingKey;
/**
* @generated from oneof slash.store.UserSetting.value
*/
value: {
/**
* @generated from field: slash.store.AccessTokensUserSetting access_tokens_user_setting = 3;
*/
value: AccessTokensUserSetting;
case: "accessTokensUserSetting";
} | { case: undefined; value?: undefined };
constructor(data?: PartialMessage<UserSetting>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.store.UserSetting";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): UserSetting;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): UserSetting;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): UserSetting;
static equals(a: UserSetting | PlainMessage<UserSetting> | undefined, b: UserSetting | PlainMessage<UserSetting> | undefined): boolean;
}
/**
* @generated from message slash.store.AccessTokensUserSetting
*/
export declare class AccessTokensUserSetting extends Message<AccessTokensUserSetting> {
/**
* @generated from field: repeated slash.store.AccessTokensUserSetting.AccessToken access_tokens = 1;
*/
accessTokens: AccessTokensUserSetting_AccessToken[];
constructor(data?: PartialMessage<AccessTokensUserSetting>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.store.AccessTokensUserSetting";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): AccessTokensUserSetting;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): AccessTokensUserSetting;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): AccessTokensUserSetting;
static equals(a: AccessTokensUserSetting | PlainMessage<AccessTokensUserSetting> | undefined, b: AccessTokensUserSetting | PlainMessage<AccessTokensUserSetting> | undefined): boolean;
}
/**
* @generated from message slash.store.AccessTokensUserSetting.AccessToken
*/
export declare class AccessTokensUserSetting_AccessToken extends Message<AccessTokensUserSetting_AccessToken> {
/**
* @generated from field: string access_token = 1;
*/
accessToken: string;
/**
* @generated from field: string description = 2;
*/
description: string;
constructor(data?: PartialMessage<AccessTokensUserSetting_AccessToken>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.store.AccessTokensUserSetting.AccessToken";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): AccessTokensUserSetting_AccessToken;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): AccessTokensUserSetting_AccessToken;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): AccessTokensUserSetting_AccessToken;
static equals(a: AccessTokensUserSetting_AccessToken | PlainMessage<AccessTokensUserSetting_AccessToken> | undefined, b: AccessTokensUserSetting_AccessToken | PlainMessage<AccessTokensUserSetting_AccessToken> | undefined): boolean;
}

View File

@ -0,0 +1,52 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file store/user_setting.proto (package slash.store, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { proto3 } from "@bufbuild/protobuf";
/**
* @generated from enum slash.store.UserSettingKey
*/
export const UserSettingKey = proto3.makeEnum(
"slash.store.UserSettingKey",
[
{no: 0, name: "USER_SETTING_KEY_UNSPECIFIED"},
{no: 1, name: "USER_SETTING_ACCESS_TOKENS"},
],
);
/**
* @generated from message slash.store.UserSetting
*/
export const UserSetting = proto3.makeMessageType(
"slash.store.UserSetting",
() => [
{ no: 1, name: "user_id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
{ no: 2, name: "key", kind: "enum", T: proto3.getEnumType(UserSettingKey) },
{ no: 3, name: "access_tokens_user_setting", kind: "message", T: AccessTokensUserSetting, oneof: "value" },
],
);
/**
* @generated from message slash.store.AccessTokensUserSetting
*/
export const AccessTokensUserSetting = proto3.makeMessageType(
"slash.store.AccessTokensUserSetting",
() => [
{ no: 1, name: "access_tokens", kind: "message", T: AccessTokensUserSetting_AccessToken, repeated: true },
],
);
/**
* @generated from message slash.store.AccessTokensUserSetting.AccessToken
*/
export const AccessTokensUserSetting_AccessToken = proto3.makeMessageType(
"slash.store.AccessTokensUserSetting.AccessToken",
() => [
{ no: 1, name: "access_token", kind: "scalar", T: 9 /* ScalarType.STRING */ },
{ no: 2, name: "description", kind: "scalar", T: 9 /* ScalarType.STRING */ },
],
{localName: "AccessTokensUserSetting_AccessToken"},
);

View File

@ -0,0 +1,8 @@
/* eslint-disable no-undef */
/** @type {import('tailwindcss').Config} */
module.exports = {
mode: "jit",
darkMode: "class",
content: ["./**/*.tsx"],
plugins: [],
};

19
extension/tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "plasmo/templates/tsconfig.base",
"exclude": [
"node_modules"
],
"include": [
".plasmo/index.d.ts",
"./**/*.ts",
"./**/*.tsx"
],
"compilerOptions": {
"paths": {
"@/*": [
"./src/*"
]
},
"baseUrl": "."
}
}

23
go.mod
View File

@ -1,4 +1,4 @@
module github.com/boojack/shortify
module github.com/boojack/slash
go 1.19
@ -10,10 +10,10 @@ require (
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.9.0
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/crypto v0.11.0
golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
)
@ -35,15 +35,16 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/friendsofgo/errors v0.9.2 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
@ -51,6 +52,8 @@ require (
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/tools v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
@ -66,8 +69,14 @@ require (
require (
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2
github.com/mssola/useragent v1.0.0
github.com/pkg/errors v0.9.1
go.deanishe.net/favicon v0.1.0
golang.org/x/mod v0.8.0
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
golang.org/x/mod v0.11.0
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.31.0
modernc.org/sqlite v1.23.1
)

49
go.sum
View File

@ -53,7 +53,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -106,6 +105,7 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -130,6 +130,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -142,6 +145,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -165,6 +169,8 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@ -191,11 +197,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.10.1 h1:rB+D8In9PWjsp1OpHaqK+t04nQv/SBD1IoIcXCg0lpY=
github.com/labstack/echo/v4 v4.10.1/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
@ -217,10 +221,11 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -304,8 +309,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -316,6 +321,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -339,8 +346,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -373,8 +380,8 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -441,8 +448,8 @@ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -450,8 +457,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -578,6 +585,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw=
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e h1:S83+ibolgyZ0bqz7KEsUOPErxcv4VzlszxY+31OfB/E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -594,6 +607,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -604,9 +619,13 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

View File

@ -1,6 +1,18 @@
package util
import "strings"
import (
"strconv"
"strings"
)
// ConvertStringToInt32 converts a string to int32.
func ConvertStringToInt32(src string) (int32, error) {
i, err := strconv.Atoi(src)
if err != nil {
return 0, err
}
return int32(i), nil
}
// HasPrefixes returns true if the string s has any of the given prefixes.
func HasPrefixes(src string, prefixes ...string) bool {

13
proto/api/v2/common.proto Normal file
View File

@ -0,0 +1,13 @@
syntax = "proto3";
package slash.api.v2;
option go_package = "gen/api/v2";
enum RowStatus {
ROW_STATUS_UNSPECIFIED = 0;
NORMAL = 1;
ARCHIVED = 2;
}

View File

@ -0,0 +1,78 @@
syntax = "proto3";
package slash.api.v2;
import "api/v2/common.proto";
import "google/api/annotations.proto";
import "google/api/client.proto";
option go_package = "gen/api/v2";
service ShortcutService {
rpc ListShortcuts(ListShortcutsRequest) returns (ListShortcutsResponse) {
option (google.api.http) = {get: "/api/v2/shortcuts"};
}
// GetShortcut returns a shortcut by name.
rpc GetShortcut(GetShortcutRequest) returns (GetShortcutResponse) {
option (google.api.http) = {get: "/api/v2/shortcuts/{name}"};
option (google.api.method_signature) = "name";
}
}
message Shortcut {
int32 id = 1;
int32 creator_id = 2;
int64 created_ts = 3;
int64 updated_ts = 4;
RowStatus row_status = 5;
string name = 6;
string link = 7;
string title = 8;
repeated string tags = 9;
string description = 10;
Visibility visibility = 11;
OpenGraphMetadata og_metadata = 12;
}
message OpenGraphMetadata {
string title = 1;
string description = 2;
string image = 3;
}
enum Visibility {
VISIBILITY_UNSPECIFIED = 0;
PRIVATE = 1;
WORKSPACE = 2;
PUBLIC = 3;
}
message ListShortcutsRequest {}
message ListShortcutsResponse {
repeated Shortcut shortcuts = 1;
}
message GetShortcutRequest {
string name = 1;
}
message GetShortcutResponse {
Shortcut shortcut = 1;
}

View File

@ -0,0 +1,104 @@
syntax = "proto3";
package slash.api.v2;
import "api/v2/common.proto";
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/protobuf/timestamp.proto";
option go_package = "gen/api/v2";
service UserService {
// GetUser returns a user by id.
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (google.api.http) = {get: "/api/v2/users/{id}"};
option (google.api.method_signature) = "id";
}
// ListUserAccessTokens returns a list of access tokens for a user.
rpc ListUserAccessTokens(ListUserAccessTokensRequest) returns (ListUserAccessTokensResponse) {
option (google.api.http) = {get: "/api/v2/users/{id}/access_tokens"};
option (google.api.method_signature) = "id";
}
// CreateUserAccessToken creates a new access token for a user.
rpc CreateUserAccessToken(CreateUserAccessTokenRequest) returns (CreateUserAccessTokenResponse) {
option (google.api.http) = {
post: "/api/v2/users/{id}/access_tokens"
body: "user_access_token"
};
option (google.api.method_signature) = "id";
}
// DeleteUserAccessToken deletes an access token for a user.
rpc DeleteUserAccessToken(DeleteUserAccessTokenRequest) returns (DeleteUserAccessTokenResponse) {
option (google.api.http) = {delete: "/api/v2/users/{id}/access_tokens/{access_token}"};
option (google.api.method_signature) = "id,access_token";
}
}
message User {
int32 id = 1;
RowStatus row_status = 2;
int64 created_ts = 3;
int64 updated_ts = 4;
Role role = 6;
string email = 7;
string nickname = 8;
}
enum Role {
ROLE_UNSPECIFIED = 0;
ADMIN = 1;
USER = 2;
}
message GetUserRequest {
int32 id = 1;
}
message GetUserResponse {
User user = 1;
}
message ListUserAccessTokensRequest {
// id is the user id.
int32 id = 1;
}
message ListUserAccessTokensResponse {
repeated UserAccessToken access_tokens = 1;
}
message CreateUserAccessTokenRequest {
// id is the user id.
int32 id = 1;
UserAccessToken user_access_token = 2;
}
message CreateUserAccessTokenResponse {
UserAccessToken access_token = 1;
}
message DeleteUserAccessTokenRequest {
// id is the user id.
int32 id = 1;
// access_token is the access token to delete.
string access_token = 2;
}
message DeleteUserAccessTokenResponse {}
message UserAccessToken {
string access_token = 1;
string description = 2;
google.protobuf.Timestamp issued_at = 3;
google.protobuf.Timestamp expires_at = 4;
}

29
proto/buf.gen.yaml Normal file
View File

@ -0,0 +1,29 @@
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/boojack/slash/proto/gen
except:
- buf.build/googleapis/googleapis
plugins:
- plugin: buf.build/protocolbuffers/go:v1.31.0
out: gen
opt:
- paths=source_relative
- plugin: buf.build/grpc/go:v1.3.0
out: gen
opt:
- paths=source_relative
- plugin: buf.build/grpc-ecosystem/gateway:v2.16.1
out: gen
opt:
- paths=source_relative
# Build the TypeScript definitions for the web and extension.
- plugin: buf.build/bufbuild/es:v1.3.0
out: ../web/src/types/proto
- plugin: buf.build/bufbuild/es:v1.3.0
out: ../extension/src/types/proto
- plugin: buf.build/community/pseudomuto-doc:v1.5.1
out: gen
opt:
- markdown,README.md,source_relative

13
proto/buf.lock Normal file
View File

@ -0,0 +1,13 @@
# Generated by buf. DO NOT EDIT.
version: v1
deps:
- remote: buf.build
owner: googleapis
repository: googleapis
commit: 711e289f6a384c4caeebaff7c6931ade
digest: shake256:e08fb55dad7469f69df00304eed31427d2d1576e9aab31e6bf86642688e04caaf0372f15fe6974cf79432779a635b3ea401ca69c943976dc42749524e4c25d94
- remote: buf.build
owner: grpc-ecosystem
repository: grpc-gateway
commit: fed2dcdcfd694403ad51cd3c94957830
digest: shake256:ed076a21e3d772892fc465ced0e4dd50f9dba86fdd4473920eaa25efa4807644e8e021be423dcfcee74bf4242e7e57422393f9b1abb10acb18ea1a5df509bb19

15
proto/buf.yaml Normal file
View File

@ -0,0 +1,15 @@
version: v1
name: buf.build/yourselfhosted/slash
breaking:
use:
- FILE
lint:
use:
- DEFAULT
except:
- ENUM_VALUE_PREFIX
- PACKAGE_DIRECTORY_MATCH
- PACKAGE_VERSION_SUFFIX
deps:
- buf.build/googleapis/googleapis
- buf.build/grpc-ecosystem/grpc-gateway

426
proto/gen/api/v2/README.md Normal file
View File

@ -0,0 +1,426 @@
# Protocol Documentation
<a name="top"></a>
## Table of Contents
- [api/v2/common.proto](#api_v2_common-proto)
- [RowStatus](#slash-api-v2-RowStatus)
- [api/v2/shortcut_service.proto](#api_v2_shortcut_service-proto)
- [GetShortcutRequest](#slash-api-v2-GetShortcutRequest)
- [GetShortcutResponse](#slash-api-v2-GetShortcutResponse)
- [ListShortcutsRequest](#slash-api-v2-ListShortcutsRequest)
- [ListShortcutsResponse](#slash-api-v2-ListShortcutsResponse)
- [OpenGraphMetadata](#slash-api-v2-OpenGraphMetadata)
- [Shortcut](#slash-api-v2-Shortcut)
- [Visibility](#slash-api-v2-Visibility)
- [ShortcutService](#slash-api-v2-ShortcutService)
- [api/v2/user_service.proto](#api_v2_user_service-proto)
- [CreateUserAccessTokenRequest](#slash-api-v2-CreateUserAccessTokenRequest)
- [CreateUserAccessTokenResponse](#slash-api-v2-CreateUserAccessTokenResponse)
- [DeleteUserAccessTokenRequest](#slash-api-v2-DeleteUserAccessTokenRequest)
- [DeleteUserAccessTokenResponse](#slash-api-v2-DeleteUserAccessTokenResponse)
- [GetUserRequest](#slash-api-v2-GetUserRequest)
- [GetUserResponse](#slash-api-v2-GetUserResponse)
- [ListUserAccessTokensRequest](#slash-api-v2-ListUserAccessTokensRequest)
- [ListUserAccessTokensResponse](#slash-api-v2-ListUserAccessTokensResponse)
- [User](#slash-api-v2-User)
- [UserAccessToken](#slash-api-v2-UserAccessToken)
- [Role](#slash-api-v2-Role)
- [UserService](#slash-api-v2-UserService)
- [Scalar Value Types](#scalar-value-types)
<a name="api_v2_common-proto"></a>
<p align="right"><a href="#top">Top</a></p>
## api/v2/common.proto
<a name="slash-api-v2-RowStatus"></a>
### RowStatus
| Name | Number | Description |
| ---- | ------ | ----------- |
| ROW_STATUS_UNSPECIFIED | 0 | |
| NORMAL | 1 | |
| ARCHIVED | 2 | |
<a name="api_v2_shortcut_service-proto"></a>
<p align="right"><a href="#top">Top</a></p>
## api/v2/shortcut_service.proto
<a name="slash-api-v2-GetShortcutRequest"></a>
### GetShortcutRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| name | [string](#string) | | |
<a name="slash-api-v2-GetShortcutResponse"></a>
### GetShortcutResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| shortcut | [Shortcut](#slash-api-v2-Shortcut) | | |
<a name="slash-api-v2-ListShortcutsRequest"></a>
### ListShortcutsRequest
<a name="slash-api-v2-ListShortcutsResponse"></a>
### ListShortcutsResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| shortcuts | [Shortcut](#slash-api-v2-Shortcut) | repeated | |
<a name="slash-api-v2-OpenGraphMetadata"></a>
### OpenGraphMetadata
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| title | [string](#string) | | |
| description | [string](#string) | | |
| image | [string](#string) | | |
<a name="slash-api-v2-Shortcut"></a>
### Shortcut
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | |
| creator_id | [int32](#int32) | | |
| created_ts | [int64](#int64) | | |
| updated_ts | [int64](#int64) | | |
| row_status | [RowStatus](#slash-api-v2-RowStatus) | | |
| name | [string](#string) | | |
| link | [string](#string) | | |
| title | [string](#string) | | |
| tags | [string](#string) | repeated | |
| description | [string](#string) | | |
| visibility | [Visibility](#slash-api-v2-Visibility) | | |
| og_metadata | [OpenGraphMetadata](#slash-api-v2-OpenGraphMetadata) | | |
<a name="slash-api-v2-Visibility"></a>
### Visibility
| Name | Number | Description |
| ---- | ------ | ----------- |
| VISIBILITY_UNSPECIFIED | 0 | |
| PRIVATE | 1 | |
| WORKSPACE | 2 | |
| PUBLIC | 3 | |
<a name="slash-api-v2-ShortcutService"></a>
### ShortcutService
| Method Name | Request Type | Response Type | Description |
| ----------- | ------------ | ------------- | ------------|
| ListShortcuts | [ListShortcutsRequest](#slash-api-v2-ListShortcutsRequest) | [ListShortcutsResponse](#slash-api-v2-ListShortcutsResponse) | |
| GetShortcut | [GetShortcutRequest](#slash-api-v2-GetShortcutRequest) | [GetShortcutResponse](#slash-api-v2-GetShortcutResponse) | GetShortcut returns a shortcut by name. |
<a name="api_v2_user_service-proto"></a>
<p align="right"><a href="#top">Top</a></p>
## api/v2/user_service.proto
<a name="slash-api-v2-CreateUserAccessTokenRequest"></a>
### CreateUserAccessTokenRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | id is the user id. |
| user_access_token | [UserAccessToken](#slash-api-v2-UserAccessToken) | | |
<a name="slash-api-v2-CreateUserAccessTokenResponse"></a>
### CreateUserAccessTokenResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| access_token | [UserAccessToken](#slash-api-v2-UserAccessToken) | | |
<a name="slash-api-v2-DeleteUserAccessTokenRequest"></a>
### DeleteUserAccessTokenRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | id is the user id. |
| access_token | [string](#string) | | access_token is the access token to delete. |
<a name="slash-api-v2-DeleteUserAccessTokenResponse"></a>
### DeleteUserAccessTokenResponse
<a name="slash-api-v2-GetUserRequest"></a>
### GetUserRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | |
<a name="slash-api-v2-GetUserResponse"></a>
### GetUserResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| user | [User](#slash-api-v2-User) | | |
<a name="slash-api-v2-ListUserAccessTokensRequest"></a>
### ListUserAccessTokensRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | id is the user id. |
<a name="slash-api-v2-ListUserAccessTokensResponse"></a>
### ListUserAccessTokensResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| access_tokens | [UserAccessToken](#slash-api-v2-UserAccessToken) | repeated | |
<a name="slash-api-v2-User"></a>
### User
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | |
| row_status | [RowStatus](#slash-api-v2-RowStatus) | | |
| created_ts | [int64](#int64) | | |
| updated_ts | [int64](#int64) | | |
| role | [Role](#slash-api-v2-Role) | | |
| email | [string](#string) | | |
| nickname | [string](#string) | | |
<a name="slash-api-v2-UserAccessToken"></a>
### UserAccessToken
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| access_token | [string](#string) | | |
| description | [string](#string) | | |
| issued_at | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | |
| expires_at | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | |
<a name="slash-api-v2-Role"></a>
### Role
| Name | Number | Description |
| ---- | ------ | ----------- |
| ROLE_UNSPECIFIED | 0 | |
| ADMIN | 1 | |
| USER | 2 | |
<a name="slash-api-v2-UserService"></a>
### UserService
| Method Name | Request Type | Response Type | Description |
| ----------- | ------------ | ------------- | ------------|
| GetUser | [GetUserRequest](#slash-api-v2-GetUserRequest) | [GetUserResponse](#slash-api-v2-GetUserResponse) | GetUser returns a user by id. |
| ListUserAccessTokens | [ListUserAccessTokensRequest](#slash-api-v2-ListUserAccessTokensRequest) | [ListUserAccessTokensResponse](#slash-api-v2-ListUserAccessTokensResponse) | ListUserAccessTokens returns a list of access tokens for a user. |
| CreateUserAccessToken | [CreateUserAccessTokenRequest](#slash-api-v2-CreateUserAccessTokenRequest) | [CreateUserAccessTokenResponse](#slash-api-v2-CreateUserAccessTokenResponse) | CreateUserAccessToken creates a new access token for a user. |
| DeleteUserAccessToken | [DeleteUserAccessTokenRequest](#slash-api-v2-DeleteUserAccessTokenRequest) | [DeleteUserAccessTokenResponse](#slash-api-v2-DeleteUserAccessTokenResponse) | DeleteUserAccessToken deletes an access token for a user. |
## Scalar Value Types
| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- |
| <a name="double" /> double | | double | double | float | float64 | double | float | Float |
| <a name="float" /> float | | float | float | float | float32 | float | float | Float |
| <a name="int32" /> int32 | Uses variable-length encoding. Inefficient for encoding negative numbers if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| <a name="int64" /> int64 | Uses variable-length encoding. Inefficient for encoding negative numbers if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| <a name="uint32" /> uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |
| <a name="uint64" /> uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |
| <a name="sint32" /> sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| <a name="sint64" /> sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| <a name="fixed32" /> fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |
| <a name="fixed64" /> fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |
| <a name="sfixed32" /> sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| <a name="sfixed64" /> sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| <a name="bool" /> bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |
| <a name="string" /> string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| <a name="bytes" /> bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |

View File

@ -0,0 +1,142 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: api/v2/common.proto
package apiv2
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type RowStatus int32
const (
RowStatus_ROW_STATUS_UNSPECIFIED RowStatus = 0
RowStatus_NORMAL RowStatus = 1
RowStatus_ARCHIVED RowStatus = 2
)
// Enum value maps for RowStatus.
var (
RowStatus_name = map[int32]string{
0: "ROW_STATUS_UNSPECIFIED",
1: "NORMAL",
2: "ARCHIVED",
}
RowStatus_value = map[string]int32{
"ROW_STATUS_UNSPECIFIED": 0,
"NORMAL": 1,
"ARCHIVED": 2,
}
)
func (x RowStatus) Enum() *RowStatus {
p := new(RowStatus)
*p = x
return p
}
func (x RowStatus) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (RowStatus) Descriptor() protoreflect.EnumDescriptor {
return file_api_v2_common_proto_enumTypes[0].Descriptor()
}
func (RowStatus) Type() protoreflect.EnumType {
return &file_api_v2_common_proto_enumTypes[0]
}
func (x RowStatus) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use RowStatus.Descriptor instead.
func (RowStatus) EnumDescriptor() ([]byte, []int) {
return file_api_v2_common_proto_rawDescGZIP(), []int{0}
}
var File_api_v2_common_proto protoreflect.FileDescriptor
var file_api_v2_common_proto_rawDesc = []byte{
0x0a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x76, 0x32, 0x2a, 0x41, 0x0a, 0x09, 0x52, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x12, 0x1a, 0x0a, 0x16, 0x52, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55,
0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06,
0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x52, 0x43, 0x48,
0x49, 0x56, 0x45, 0x44, 0x10, 0x02, 0x42, 0xa2, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x73,
0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x0b, 0x43, 0x6f, 0x6d,
0x6d, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x6a, 0x61, 0x63, 0x6b, 0x2f, 0x73,
0x6c, 0x61, 0x73, 0x68, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x32, 0xa2, 0x02, 0x03, 0x53, 0x41,
0x58, 0xaa, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x32,
0xca, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0xe2,
0x02, 0x18, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0x5c, 0x47,
0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x53, 0x6c, 0x61,
0x73, 0x68, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
file_api_v2_common_proto_rawDescOnce sync.Once
file_api_v2_common_proto_rawDescData = file_api_v2_common_proto_rawDesc
)
func file_api_v2_common_proto_rawDescGZIP() []byte {
file_api_v2_common_proto_rawDescOnce.Do(func() {
file_api_v2_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_v2_common_proto_rawDescData)
})
return file_api_v2_common_proto_rawDescData
}
var file_api_v2_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_api_v2_common_proto_goTypes = []interface{}{
(RowStatus)(0), // 0: slash.api.v2.RowStatus
}
var file_api_v2_common_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_api_v2_common_proto_init() }
func file_api_v2_common_proto_init() {
if File_api_v2_common_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_v2_common_proto_rawDesc,
NumEnums: 1,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_api_v2_common_proto_goTypes,
DependencyIndexes: file_api_v2_common_proto_depIdxs,
EnumInfos: file_api_v2_common_proto_enumTypes,
}.Build()
File_api_v2_common_proto = out.File
file_api_v2_common_proto_rawDesc = nil
file_api_v2_common_proto_goTypes = nil
file_api_v2_common_proto_depIdxs = nil
}

View File

@ -0,0 +1,685 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: api/v2/shortcut_service.proto
package apiv2
import (
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Visibility int32
const (
Visibility_VISIBILITY_UNSPECIFIED Visibility = 0
Visibility_PRIVATE Visibility = 1
Visibility_WORKSPACE Visibility = 2
Visibility_PUBLIC Visibility = 3
)
// Enum value maps for Visibility.
var (
Visibility_name = map[int32]string{
0: "VISIBILITY_UNSPECIFIED",
1: "PRIVATE",
2: "WORKSPACE",
3: "PUBLIC",
}
Visibility_value = map[string]int32{
"VISIBILITY_UNSPECIFIED": 0,
"PRIVATE": 1,
"WORKSPACE": 2,
"PUBLIC": 3,
}
)
func (x Visibility) Enum() *Visibility {
p := new(Visibility)
*p = x
return p
}
func (x Visibility) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Visibility) Descriptor() protoreflect.EnumDescriptor {
return file_api_v2_shortcut_service_proto_enumTypes[0].Descriptor()
}
func (Visibility) Type() protoreflect.EnumType {
return &file_api_v2_shortcut_service_proto_enumTypes[0]
}
func (x Visibility) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Visibility.Descriptor instead.
func (Visibility) EnumDescriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{0}
}
type Shortcut struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
CreatorId int32 `protobuf:"varint,2,opt,name=creator_id,json=creatorId,proto3" json:"creator_id,omitempty"`
CreatedTs int64 `protobuf:"varint,3,opt,name=created_ts,json=createdTs,proto3" json:"created_ts,omitempty"`
UpdatedTs int64 `protobuf:"varint,4,opt,name=updated_ts,json=updatedTs,proto3" json:"updated_ts,omitempty"`
RowStatus RowStatus `protobuf:"varint,5,opt,name=row_status,json=rowStatus,proto3,enum=slash.api.v2.RowStatus" json:"row_status,omitempty"`
Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
Link string `protobuf:"bytes,7,opt,name=link,proto3" json:"link,omitempty"`
Title string `protobuf:"bytes,8,opt,name=title,proto3" json:"title,omitempty"`
Tags []string `protobuf:"bytes,9,rep,name=tags,proto3" json:"tags,omitempty"`
Description string `protobuf:"bytes,10,opt,name=description,proto3" json:"description,omitempty"`
Visibility Visibility `protobuf:"varint,11,opt,name=visibility,proto3,enum=slash.api.v2.Visibility" json:"visibility,omitempty"`
OgMetadata *OpenGraphMetadata `protobuf:"bytes,12,opt,name=og_metadata,json=ogMetadata,proto3" json:"og_metadata,omitempty"`
}
func (x *Shortcut) Reset() {
*x = Shortcut{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Shortcut) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Shortcut) ProtoMessage() {}
func (x *Shortcut) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_service_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Shortcut.ProtoReflect.Descriptor instead.
func (*Shortcut) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{0}
}
func (x *Shortcut) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *Shortcut) GetCreatorId() int32 {
if x != nil {
return x.CreatorId
}
return 0
}
func (x *Shortcut) GetCreatedTs() int64 {
if x != nil {
return x.CreatedTs
}
return 0
}
func (x *Shortcut) GetUpdatedTs() int64 {
if x != nil {
return x.UpdatedTs
}
return 0
}
func (x *Shortcut) GetRowStatus() RowStatus {
if x != nil {
return x.RowStatus
}
return RowStatus_ROW_STATUS_UNSPECIFIED
}
func (x *Shortcut) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Shortcut) GetLink() string {
if x != nil {
return x.Link
}
return ""
}
func (x *Shortcut) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
func (x *Shortcut) GetTags() []string {
if x != nil {
return x.Tags
}
return nil
}
func (x *Shortcut) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *Shortcut) GetVisibility() Visibility {
if x != nil {
return x.Visibility
}
return Visibility_VISIBILITY_UNSPECIFIED
}
func (x *Shortcut) GetOgMetadata() *OpenGraphMetadata {
if x != nil {
return x.OgMetadata
}
return nil
}
type OpenGraphMetadata struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
Image string `protobuf:"bytes,3,opt,name=image,proto3" json:"image,omitempty"`
}
func (x *OpenGraphMetadata) Reset() {
*x = OpenGraphMetadata{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OpenGraphMetadata) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OpenGraphMetadata) ProtoMessage() {}
func (x *OpenGraphMetadata) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_service_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use OpenGraphMetadata.ProtoReflect.Descriptor instead.
func (*OpenGraphMetadata) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{1}
}
func (x *OpenGraphMetadata) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
func (x *OpenGraphMetadata) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *OpenGraphMetadata) GetImage() string {
if x != nil {
return x.Image
}
return ""
}
type ListShortcutsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ListShortcutsRequest) Reset() {
*x = ListShortcutsRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListShortcutsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListShortcutsRequest) ProtoMessage() {}
func (x *ListShortcutsRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_service_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListShortcutsRequest.ProtoReflect.Descriptor instead.
func (*ListShortcutsRequest) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{2}
}
type ListShortcutsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Shortcuts []*Shortcut `protobuf:"bytes,1,rep,name=shortcuts,proto3" json:"shortcuts,omitempty"`
}
func (x *ListShortcutsResponse) Reset() {
*x = ListShortcutsResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListShortcutsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListShortcutsResponse) ProtoMessage() {}
func (x *ListShortcutsResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_service_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListShortcutsResponse.ProtoReflect.Descriptor instead.
func (*ListShortcutsResponse) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{3}
}
func (x *ListShortcutsResponse) GetShortcuts() []*Shortcut {
if x != nil {
return x.Shortcuts
}
return nil
}
type GetShortcutRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *GetShortcutRequest) Reset() {
*x = GetShortcutRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetShortcutRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetShortcutRequest) ProtoMessage() {}
func (x *GetShortcutRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_service_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetShortcutRequest.ProtoReflect.Descriptor instead.
func (*GetShortcutRequest) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{4}
}
func (x *GetShortcutRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type GetShortcutResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Shortcut *Shortcut `protobuf:"bytes,1,opt,name=shortcut,proto3" json:"shortcut,omitempty"`
}
func (x *GetShortcutResponse) Reset() {
*x = GetShortcutResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetShortcutResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetShortcutResponse) ProtoMessage() {}
func (x *GetShortcutResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_service_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetShortcutResponse.ProtoReflect.Descriptor instead.
func (*GetShortcutResponse) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{5}
}
func (x *GetShortcutResponse) GetShortcut() *Shortcut {
if x != nil {
return x.Shortcut
}
return nil
}
var File_api_v2_shortcut_service_proto protoreflect.FileDescriptor
var file_api_v2_shortcut_service_proto_rawDesc = []byte{
0x0a, 0x1d, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75,
0x74, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x0c, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x1a, 0x13, 0x61,
0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61,
0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x1a, 0x17, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6c, 0x69,
0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9f, 0x03, 0x0a, 0x08, 0x53, 0x68,
0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f,
0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61,
0x74, 0x6f, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
0x5f, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x64, 0x54, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f,
0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
0x64, 0x54, 0x73, 0x12, 0x36, 0x0a, 0x0a, 0x72, 0x6f, 0x77, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x52, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x52, 0x09, 0x72, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c,
0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67,
0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x20, 0x0a,
0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x38, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x0b, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x76, 0x32, 0x2e, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76,
0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x40, 0x0a, 0x0b, 0x6f, 0x67, 0x5f,
0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f,
0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4f, 0x70,
0x65, 0x6e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52,
0x0a, 0x6f, 0x67, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x61, 0x0a, 0x11, 0x4f,
0x70, 0x65, 0x6e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x16,
0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x68,
0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x34, 0x0a, 0x09, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
0x32, 0x2e, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x52, 0x09, 0x73, 0x68, 0x6f, 0x72,
0x74, 0x63, 0x75, 0x74, 0x73, 0x22, 0x28, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x53, 0x68, 0x6f, 0x72,
0x74, 0x63, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22,
0x49, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63,
0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74,
0x52, 0x08, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x2a, 0x50, 0x0a, 0x0a, 0x56, 0x69,
0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x49, 0x53, 0x49,
0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10,
0x01, 0x12, 0x0d, 0x0a, 0x09, 0x57, 0x4f, 0x52, 0x4b, 0x53, 0x50, 0x41, 0x43, 0x45, 0x10, 0x02,
0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x03, 0x32, 0x83, 0x02, 0x0a,
0x0f, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x12, 0x73, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74,
0x73, 0x12, 0x22, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32,
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75,
0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93,
0x02, 0x13, 0x12, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x73, 0x68, 0x6f, 0x72,
0x74, 0x63, 0x75, 0x74, 0x73, 0x12, 0x7b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x68, 0x6f, 0x72,
0x74, 0x63, 0x75, 0x74, 0x12, 0x20, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0xda, 0x41, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76,
0x32, 0x2f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d,
0x65, 0x7d, 0x42, 0xab, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x14, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75,
0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a,
0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x6a,
0x61, 0x63, 0x6b, 0x2f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
0x67, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x32,
0xa2, 0x02, 0x03, 0x53, 0x41, 0x58, 0xaa, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x41,
0x70, 0x69, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70,
0x69, 0x5c, 0x56, 0x32, 0xe2, 0x02, 0x18, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70, 0x69,
0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea,
0x02, 0x0e, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_api_v2_shortcut_service_proto_rawDescOnce sync.Once
file_api_v2_shortcut_service_proto_rawDescData = file_api_v2_shortcut_service_proto_rawDesc
)
func file_api_v2_shortcut_service_proto_rawDescGZIP() []byte {
file_api_v2_shortcut_service_proto_rawDescOnce.Do(func() {
file_api_v2_shortcut_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_v2_shortcut_service_proto_rawDescData)
})
return file_api_v2_shortcut_service_proto_rawDescData
}
var file_api_v2_shortcut_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_api_v2_shortcut_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_api_v2_shortcut_service_proto_goTypes = []interface{}{
(Visibility)(0), // 0: slash.api.v2.Visibility
(*Shortcut)(nil), // 1: slash.api.v2.Shortcut
(*OpenGraphMetadata)(nil), // 2: slash.api.v2.OpenGraphMetadata
(*ListShortcutsRequest)(nil), // 3: slash.api.v2.ListShortcutsRequest
(*ListShortcutsResponse)(nil), // 4: slash.api.v2.ListShortcutsResponse
(*GetShortcutRequest)(nil), // 5: slash.api.v2.GetShortcutRequest
(*GetShortcutResponse)(nil), // 6: slash.api.v2.GetShortcutResponse
(RowStatus)(0), // 7: slash.api.v2.RowStatus
}
var file_api_v2_shortcut_service_proto_depIdxs = []int32{
7, // 0: slash.api.v2.Shortcut.row_status:type_name -> slash.api.v2.RowStatus
0, // 1: slash.api.v2.Shortcut.visibility:type_name -> slash.api.v2.Visibility
2, // 2: slash.api.v2.Shortcut.og_metadata:type_name -> slash.api.v2.OpenGraphMetadata
1, // 3: slash.api.v2.ListShortcutsResponse.shortcuts:type_name -> slash.api.v2.Shortcut
1, // 4: slash.api.v2.GetShortcutResponse.shortcut:type_name -> slash.api.v2.Shortcut
3, // 5: slash.api.v2.ShortcutService.ListShortcuts:input_type -> slash.api.v2.ListShortcutsRequest
5, // 6: slash.api.v2.ShortcutService.GetShortcut:input_type -> slash.api.v2.GetShortcutRequest
4, // 7: slash.api.v2.ShortcutService.ListShortcuts:output_type -> slash.api.v2.ListShortcutsResponse
6, // 8: slash.api.v2.ShortcutService.GetShortcut:output_type -> slash.api.v2.GetShortcutResponse
7, // [7:9] is the sub-list for method output_type
5, // [5:7] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_api_v2_shortcut_service_proto_init() }
func file_api_v2_shortcut_service_proto_init() {
if File_api_v2_shortcut_service_proto != nil {
return
}
file_api_v2_common_proto_init()
if !protoimpl.UnsafeEnabled {
file_api_v2_shortcut_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Shortcut); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_shortcut_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OpenGraphMetadata); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_shortcut_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListShortcutsRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_shortcut_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListShortcutsResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_shortcut_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetShortcutRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_shortcut_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetShortcutResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_v2_shortcut_service_proto_rawDesc,
NumEnums: 1,
NumMessages: 6,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_api_v2_shortcut_service_proto_goTypes,
DependencyIndexes: file_api_v2_shortcut_service_proto_depIdxs,
EnumInfos: file_api_v2_shortcut_service_proto_enumTypes,
MessageInfos: file_api_v2_shortcut_service_proto_msgTypes,
}.Build()
File_api_v2_shortcut_service_proto = out.File
file_api_v2_shortcut_service_proto_rawDesc = nil
file_api_v2_shortcut_service_proto_goTypes = nil
file_api_v2_shortcut_service_proto_depIdxs = nil
}

View File

@ -0,0 +1,258 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: api/v2/shortcut_service.proto
/*
Package apiv2 is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package apiv2
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_ShortcutService_ListShortcuts_0(ctx context.Context, marshaler runtime.Marshaler, client ShortcutServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListShortcutsRequest
var metadata runtime.ServerMetadata
msg, err := client.ListShortcuts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ShortcutService_ListShortcuts_0(ctx context.Context, marshaler runtime.Marshaler, server ShortcutServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListShortcutsRequest
var metadata runtime.ServerMetadata
msg, err := server.ListShortcuts(ctx, &protoReq)
return msg, metadata, err
}
func request_ShortcutService_GetShortcut_0(ctx context.Context, marshaler runtime.Marshaler, client ShortcutServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetShortcutRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
msg, err := client.GetShortcut(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ShortcutService_GetShortcut_0(ctx context.Context, marshaler runtime.Marshaler, server ShortcutServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetShortcutRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
msg, err := server.GetShortcut(ctx, &protoReq)
return msg, metadata, err
}
// RegisterShortcutServiceHandlerServer registers the http handlers for service ShortcutService to "mux".
// UnaryRPC :call ShortcutServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterShortcutServiceHandlerFromEndpoint instead.
func RegisterShortcutServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ShortcutServiceServer) error {
mux.Handle("GET", pattern_ShortcutService_ListShortcuts_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/slash.api.v2.ShortcutService/ListShortcuts", runtime.WithHTTPPathPattern("/api/v2/shortcuts"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ShortcutService_ListShortcuts_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_ShortcutService_ListShortcuts_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ShortcutService_GetShortcut_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/slash.api.v2.ShortcutService/GetShortcut", runtime.WithHTTPPathPattern("/api/v2/shortcuts/{name}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ShortcutService_GetShortcut_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_ShortcutService_GetShortcut_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterShortcutServiceHandlerFromEndpoint is same as RegisterShortcutServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterShortcutServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterShortcutServiceHandler(ctx, mux, conn)
}
// RegisterShortcutServiceHandler registers the http handlers for service ShortcutService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterShortcutServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterShortcutServiceHandlerClient(ctx, mux, NewShortcutServiceClient(conn))
}
// RegisterShortcutServiceHandlerClient registers the http handlers for service ShortcutService
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ShortcutServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ShortcutServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "ShortcutServiceClient" to call the correct interceptors.
func RegisterShortcutServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ShortcutServiceClient) error {
mux.Handle("GET", pattern_ShortcutService_ListShortcuts_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/slash.api.v2.ShortcutService/ListShortcuts", runtime.WithHTTPPathPattern("/api/v2/shortcuts"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ShortcutService_ListShortcuts_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ShortcutService_ListShortcuts_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ShortcutService_GetShortcut_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/slash.api.v2.ShortcutService/GetShortcut", runtime.WithHTTPPathPattern("/api/v2/shortcuts/{name}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ShortcutService_GetShortcut_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ShortcutService_GetShortcut_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_ShortcutService_ListShortcuts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v2", "shortcuts"}, ""))
pattern_ShortcutService_GetShortcut_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "shortcuts", "name"}, ""))
)
var (
forward_ShortcutService_ListShortcuts_0 = runtime.ForwardResponseMessage
forward_ShortcutService_GetShortcut_0 = runtime.ForwardResponseMessage
)

View File

@ -0,0 +1,148 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: api/v2/shortcut_service.proto
package apiv2
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
ShortcutService_ListShortcuts_FullMethodName = "/slash.api.v2.ShortcutService/ListShortcuts"
ShortcutService_GetShortcut_FullMethodName = "/slash.api.v2.ShortcutService/GetShortcut"
)
// ShortcutServiceClient is the client API for ShortcutService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ShortcutServiceClient interface {
ListShortcuts(ctx context.Context, in *ListShortcutsRequest, opts ...grpc.CallOption) (*ListShortcutsResponse, error)
// GetShortcut returns a shortcut by name.
GetShortcut(ctx context.Context, in *GetShortcutRequest, opts ...grpc.CallOption) (*GetShortcutResponse, error)
}
type shortcutServiceClient struct {
cc grpc.ClientConnInterface
}
func NewShortcutServiceClient(cc grpc.ClientConnInterface) ShortcutServiceClient {
return &shortcutServiceClient{cc}
}
func (c *shortcutServiceClient) ListShortcuts(ctx context.Context, in *ListShortcutsRequest, opts ...grpc.CallOption) (*ListShortcutsResponse, error) {
out := new(ListShortcutsResponse)
err := c.cc.Invoke(ctx, ShortcutService_ListShortcuts_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *shortcutServiceClient) GetShortcut(ctx context.Context, in *GetShortcutRequest, opts ...grpc.CallOption) (*GetShortcutResponse, error) {
out := new(GetShortcutResponse)
err := c.cc.Invoke(ctx, ShortcutService_GetShortcut_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ShortcutServiceServer is the server API for ShortcutService service.
// All implementations must embed UnimplementedShortcutServiceServer
// for forward compatibility
type ShortcutServiceServer interface {
ListShortcuts(context.Context, *ListShortcutsRequest) (*ListShortcutsResponse, error)
// GetShortcut returns a shortcut by name.
GetShortcut(context.Context, *GetShortcutRequest) (*GetShortcutResponse, error)
mustEmbedUnimplementedShortcutServiceServer()
}
// UnimplementedShortcutServiceServer must be embedded to have forward compatible implementations.
type UnimplementedShortcutServiceServer struct {
}
func (UnimplementedShortcutServiceServer) ListShortcuts(context.Context, *ListShortcutsRequest) (*ListShortcutsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListShortcuts not implemented")
}
func (UnimplementedShortcutServiceServer) GetShortcut(context.Context, *GetShortcutRequest) (*GetShortcutResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetShortcut not implemented")
}
func (UnimplementedShortcutServiceServer) mustEmbedUnimplementedShortcutServiceServer() {}
// UnsafeShortcutServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ShortcutServiceServer will
// result in compilation errors.
type UnsafeShortcutServiceServer interface {
mustEmbedUnimplementedShortcutServiceServer()
}
func RegisterShortcutServiceServer(s grpc.ServiceRegistrar, srv ShortcutServiceServer) {
s.RegisterService(&ShortcutService_ServiceDesc, srv)
}
func _ShortcutService_ListShortcuts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListShortcutsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ShortcutServiceServer).ListShortcuts(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ShortcutService_ListShortcuts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ShortcutServiceServer).ListShortcuts(ctx, req.(*ListShortcutsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ShortcutService_GetShortcut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetShortcutRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ShortcutServiceServer).GetShortcut(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ShortcutService_GetShortcut_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ShortcutServiceServer).GetShortcut(ctx, req.(*GetShortcutRequest))
}
return interceptor(ctx, in, info, handler)
}
// ShortcutService_ServiceDesc is the grpc.ServiceDesc for ShortcutService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ShortcutService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "slash.api.v2.ShortcutService",
HandlerType: (*ShortcutServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ListShortcuts",
Handler: _ShortcutService_ListShortcuts_Handler,
},
{
MethodName: "GetShortcut",
Handler: _ShortcutService_GetShortcut_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/v2/shortcut_service.proto",
}

View File

@ -0,0 +1,960 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: api/v2/user_service.proto
package apiv2
import (
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Role int32
const (
Role_ROLE_UNSPECIFIED Role = 0
Role_ADMIN Role = 1
Role_USER Role = 2
)
// Enum value maps for Role.
var (
Role_name = map[int32]string{
0: "ROLE_UNSPECIFIED",
1: "ADMIN",
2: "USER",
}
Role_value = map[string]int32{
"ROLE_UNSPECIFIED": 0,
"ADMIN": 1,
"USER": 2,
}
)
func (x Role) Enum() *Role {
p := new(Role)
*p = x
return p
}
func (x Role) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Role) Descriptor() protoreflect.EnumDescriptor {
return file_api_v2_user_service_proto_enumTypes[0].Descriptor()
}
func (Role) Type() protoreflect.EnumType {
return &file_api_v2_user_service_proto_enumTypes[0]
}
func (x Role) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Role.Descriptor instead.
func (Role) EnumDescriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{0}
}
type User struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
RowStatus RowStatus `protobuf:"varint,2,opt,name=row_status,json=rowStatus,proto3,enum=slash.api.v2.RowStatus" json:"row_status,omitempty"`
CreatedTs int64 `protobuf:"varint,3,opt,name=created_ts,json=createdTs,proto3" json:"created_ts,omitempty"`
UpdatedTs int64 `protobuf:"varint,4,opt,name=updated_ts,json=updatedTs,proto3" json:"updated_ts,omitempty"`
Role Role `protobuf:"varint,6,opt,name=role,proto3,enum=slash.api.v2.Role" json:"role,omitempty"`
Email string `protobuf:"bytes,7,opt,name=email,proto3" json:"email,omitempty"`
Nickname string `protobuf:"bytes,8,opt,name=nickname,proto3" json:"nickname,omitempty"`
}
func (x *User) Reset() {
*x = User{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *User) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*User) ProtoMessage() {}
func (x *User) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use User.ProtoReflect.Descriptor instead.
func (*User) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{0}
}
func (x *User) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *User) GetRowStatus() RowStatus {
if x != nil {
return x.RowStatus
}
return RowStatus_ROW_STATUS_UNSPECIFIED
}
func (x *User) GetCreatedTs() int64 {
if x != nil {
return x.CreatedTs
}
return 0
}
func (x *User) GetUpdatedTs() int64 {
if x != nil {
return x.UpdatedTs
}
return 0
}
func (x *User) GetRole() Role {
if x != nil {
return x.Role
}
return Role_ROLE_UNSPECIFIED
}
func (x *User) GetEmail() string {
if x != nil {
return x.Email
}
return ""
}
func (x *User) GetNickname() string {
if x != nil {
return x.Nickname
}
return ""
}
type GetUserRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
}
func (x *GetUserRequest) Reset() {
*x = GetUserRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetUserRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetUserRequest) ProtoMessage() {}
func (x *GetUserRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetUserRequest.ProtoReflect.Descriptor instead.
func (*GetUserRequest) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{1}
}
func (x *GetUserRequest) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
type GetUserResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
}
func (x *GetUserResponse) Reset() {
*x = GetUserResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetUserResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetUserResponse) ProtoMessage() {}
func (x *GetUserResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetUserResponse.ProtoReflect.Descriptor instead.
func (*GetUserResponse) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{2}
}
func (x *GetUserResponse) GetUser() *User {
if x != nil {
return x.User
}
return nil
}
type ListUserAccessTokensRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// id is the user id.
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
}
func (x *ListUserAccessTokensRequest) Reset() {
*x = ListUserAccessTokensRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListUserAccessTokensRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListUserAccessTokensRequest) ProtoMessage() {}
func (x *ListUserAccessTokensRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListUserAccessTokensRequest.ProtoReflect.Descriptor instead.
func (*ListUserAccessTokensRequest) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{3}
}
func (x *ListUserAccessTokensRequest) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
type ListUserAccessTokensResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AccessTokens []*UserAccessToken `protobuf:"bytes,1,rep,name=access_tokens,json=accessTokens,proto3" json:"access_tokens,omitempty"`
}
func (x *ListUserAccessTokensResponse) Reset() {
*x = ListUserAccessTokensResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListUserAccessTokensResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListUserAccessTokensResponse) ProtoMessage() {}
func (x *ListUserAccessTokensResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListUserAccessTokensResponse.ProtoReflect.Descriptor instead.
func (*ListUserAccessTokensResponse) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{4}
}
func (x *ListUserAccessTokensResponse) GetAccessTokens() []*UserAccessToken {
if x != nil {
return x.AccessTokens
}
return nil
}
type CreateUserAccessTokenRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// id is the user id.
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
UserAccessToken *UserAccessToken `protobuf:"bytes,2,opt,name=user_access_token,json=userAccessToken,proto3" json:"user_access_token,omitempty"`
}
func (x *CreateUserAccessTokenRequest) Reset() {
*x = CreateUserAccessTokenRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CreateUserAccessTokenRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CreateUserAccessTokenRequest) ProtoMessage() {}
func (x *CreateUserAccessTokenRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CreateUserAccessTokenRequest.ProtoReflect.Descriptor instead.
func (*CreateUserAccessTokenRequest) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{5}
}
func (x *CreateUserAccessTokenRequest) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *CreateUserAccessTokenRequest) GetUserAccessToken() *UserAccessToken {
if x != nil {
return x.UserAccessToken
}
return nil
}
type CreateUserAccessTokenResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AccessToken *UserAccessToken `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"`
}
func (x *CreateUserAccessTokenResponse) Reset() {
*x = CreateUserAccessTokenResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CreateUserAccessTokenResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CreateUserAccessTokenResponse) ProtoMessage() {}
func (x *CreateUserAccessTokenResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CreateUserAccessTokenResponse.ProtoReflect.Descriptor instead.
func (*CreateUserAccessTokenResponse) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{6}
}
func (x *CreateUserAccessTokenResponse) GetAccessToken() *UserAccessToken {
if x != nil {
return x.AccessToken
}
return nil
}
type DeleteUserAccessTokenRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// id is the user id.
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
// access_token is the access token to delete.
AccessToken string `protobuf:"bytes,2,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"`
}
func (x *DeleteUserAccessTokenRequest) Reset() {
*x = DeleteUserAccessTokenRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteUserAccessTokenRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteUserAccessTokenRequest) ProtoMessage() {}
func (x *DeleteUserAccessTokenRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteUserAccessTokenRequest.ProtoReflect.Descriptor instead.
func (*DeleteUserAccessTokenRequest) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{7}
}
func (x *DeleteUserAccessTokenRequest) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *DeleteUserAccessTokenRequest) GetAccessToken() string {
if x != nil {
return x.AccessToken
}
return ""
}
type DeleteUserAccessTokenResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *DeleteUserAccessTokenResponse) Reset() {
*x = DeleteUserAccessTokenResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteUserAccessTokenResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteUserAccessTokenResponse) ProtoMessage() {}
func (x *DeleteUserAccessTokenResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteUserAccessTokenResponse.ProtoReflect.Descriptor instead.
func (*DeleteUserAccessTokenResponse) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{8}
}
type UserAccessToken struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"`
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
IssuedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"`
ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
}
func (x *UserAccessToken) Reset() {
*x = UserAccessToken{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_user_service_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UserAccessToken) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserAccessToken) ProtoMessage() {}
func (x *UserAccessToken) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_user_service_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserAccessToken.ProtoReflect.Descriptor instead.
func (*UserAccessToken) Descriptor() ([]byte, []int) {
return file_api_v2_user_service_proto_rawDescGZIP(), []int{9}
}
func (x *UserAccessToken) GetAccessToken() string {
if x != nil {
return x.AccessToken
}
return ""
}
func (x *UserAccessToken) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *UserAccessToken) GetIssuedAt() *timestamppb.Timestamp {
if x != nil {
return x.IssuedAt
}
return nil
}
func (x *UserAccessToken) GetExpiresAt() *timestamppb.Timestamp {
if x != nil {
return x.ExpiresAt
}
return nil
}
var File_api_v2_user_service_proto protoreflect.FileDescriptor
var file_api_v2_user_service_proto_rawDesc = []byte{
0x0a, 0x19, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x73, 0x6c, 0x61,
0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x1a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x76,
0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe6, 0x01, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12,
0x36, 0x0a, 0x0a, 0x72, 0x6f, 0x77, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x76, 0x32, 0x2e, 0x52, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x09, 0x72, 0x6f,
0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x64, 0x5f, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65,
0x61, 0x74, 0x65, 0x64, 0x54, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
0x64, 0x5f, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61,
0x74, 0x65, 0x64, 0x54, 0x73, 0x12, 0x26, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x06, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x76, 0x32, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x14, 0x0a,
0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d,
0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x22,
0x20, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69,
0x64, 0x22, 0x39, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
0x32, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2d, 0x0a, 0x1b,
0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f,
0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x22, 0x62, 0x0a, 0x1c, 0x4c,
0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b,
0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x61,
0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
0x32, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65,
0x6e, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x22,
0x79, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12,
0x49, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74,
0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x6c, 0x61,
0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63,
0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x41,
0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x61, 0x0a, 0x1d, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f,
0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0c, 0x61,
0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32,
0x2e, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x51, 0x0a,
0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73,
0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a,
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a,
0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
0x22, 0x1f, 0x0a, 0x1d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63,
0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0xca, 0x01, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f,
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63,
0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64,
0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x69, 0x73,
0x73, 0x75, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x69, 0x73, 0x73, 0x75, 0x65,
0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x2a, 0x31,
0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x55,
0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05,
0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x55, 0x53, 0x45, 0x52, 0x10,
0x02, 0x32, 0x88, 0x05, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x67, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x73,
0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x55,
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x73, 0x6c, 0x61,
0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65,
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0xda, 0x41, 0x02, 0x69, 0x64,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f,
0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x9c, 0x01, 0x0a, 0x14, 0x4c,
0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b,
0x65, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73,
0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a,
0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65,
0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2d, 0xda, 0x41, 0x02, 0x69,
0x64, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32,
0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x2f, 0x61, 0x63, 0x63, 0x65,
0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0xb2, 0x01, 0x0a, 0x15, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f,
0x6b, 0x65, 0x6e, 0x12, 0x2a, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x2b, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54,
0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0xda, 0x41,
0x02, 0x69, 0x64, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x35, 0x3a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x5f,
0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x20, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d,
0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0xbb,
0x01, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2a, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73,
0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63,
0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x49, 0xda, 0x41, 0x0f, 0x69, 0x64, 0x2c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f,
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x31, 0x2a, 0x2f, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x76, 0x32, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x2f,
0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2f, 0x7b, 0x61,
0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x7d, 0x42, 0xa7, 0x01, 0x0a,
0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
0x32, 0x42, 0x10, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72,
0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x6a, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2f,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32,
0x3b, 0x61, 0x70, 0x69, 0x76, 0x32, 0xa2, 0x02, 0x03, 0x53, 0x41, 0x58, 0xaa, 0x02, 0x0c, 0x53,
0x6c, 0x61, 0x73, 0x68, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x0c, 0x53, 0x6c,
0x61, 0x73, 0x68, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0xe2, 0x02, 0x18, 0x53, 0x6c, 0x61,
0x73, 0x68, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74,
0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x3a, 0x3a, 0x41,
0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_api_v2_user_service_proto_rawDescOnce sync.Once
file_api_v2_user_service_proto_rawDescData = file_api_v2_user_service_proto_rawDesc
)
func file_api_v2_user_service_proto_rawDescGZIP() []byte {
file_api_v2_user_service_proto_rawDescOnce.Do(func() {
file_api_v2_user_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_v2_user_service_proto_rawDescData)
})
return file_api_v2_user_service_proto_rawDescData
}
var file_api_v2_user_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_api_v2_user_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_api_v2_user_service_proto_goTypes = []interface{}{
(Role)(0), // 0: slash.api.v2.Role
(*User)(nil), // 1: slash.api.v2.User
(*GetUserRequest)(nil), // 2: slash.api.v2.GetUserRequest
(*GetUserResponse)(nil), // 3: slash.api.v2.GetUserResponse
(*ListUserAccessTokensRequest)(nil), // 4: slash.api.v2.ListUserAccessTokensRequest
(*ListUserAccessTokensResponse)(nil), // 5: slash.api.v2.ListUserAccessTokensResponse
(*CreateUserAccessTokenRequest)(nil), // 6: slash.api.v2.CreateUserAccessTokenRequest
(*CreateUserAccessTokenResponse)(nil), // 7: slash.api.v2.CreateUserAccessTokenResponse
(*DeleteUserAccessTokenRequest)(nil), // 8: slash.api.v2.DeleteUserAccessTokenRequest
(*DeleteUserAccessTokenResponse)(nil), // 9: slash.api.v2.DeleteUserAccessTokenResponse
(*UserAccessToken)(nil), // 10: slash.api.v2.UserAccessToken
(RowStatus)(0), // 11: slash.api.v2.RowStatus
(*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
}
var file_api_v2_user_service_proto_depIdxs = []int32{
11, // 0: slash.api.v2.User.row_status:type_name -> slash.api.v2.RowStatus
0, // 1: slash.api.v2.User.role:type_name -> slash.api.v2.Role
1, // 2: slash.api.v2.GetUserResponse.user:type_name -> slash.api.v2.User
10, // 3: slash.api.v2.ListUserAccessTokensResponse.access_tokens:type_name -> slash.api.v2.UserAccessToken
10, // 4: slash.api.v2.CreateUserAccessTokenRequest.user_access_token:type_name -> slash.api.v2.UserAccessToken
10, // 5: slash.api.v2.CreateUserAccessTokenResponse.access_token:type_name -> slash.api.v2.UserAccessToken
12, // 6: slash.api.v2.UserAccessToken.issued_at:type_name -> google.protobuf.Timestamp
12, // 7: slash.api.v2.UserAccessToken.expires_at:type_name -> google.protobuf.Timestamp
2, // 8: slash.api.v2.UserService.GetUser:input_type -> slash.api.v2.GetUserRequest
4, // 9: slash.api.v2.UserService.ListUserAccessTokens:input_type -> slash.api.v2.ListUserAccessTokensRequest
6, // 10: slash.api.v2.UserService.CreateUserAccessToken:input_type -> slash.api.v2.CreateUserAccessTokenRequest
8, // 11: slash.api.v2.UserService.DeleteUserAccessToken:input_type -> slash.api.v2.DeleteUserAccessTokenRequest
3, // 12: slash.api.v2.UserService.GetUser:output_type -> slash.api.v2.GetUserResponse
5, // 13: slash.api.v2.UserService.ListUserAccessTokens:output_type -> slash.api.v2.ListUserAccessTokensResponse
7, // 14: slash.api.v2.UserService.CreateUserAccessToken:output_type -> slash.api.v2.CreateUserAccessTokenResponse
9, // 15: slash.api.v2.UserService.DeleteUserAccessToken:output_type -> slash.api.v2.DeleteUserAccessTokenResponse
12, // [12:16] is the sub-list for method output_type
8, // [8:12] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
}
func init() { file_api_v2_user_service_proto_init() }
func file_api_v2_user_service_proto_init() {
if File_api_v2_user_service_proto != nil {
return
}
file_api_v2_common_proto_init()
if !protoimpl.UnsafeEnabled {
file_api_v2_user_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*User); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_user_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetUserRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_user_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetUserResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_user_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListUserAccessTokensRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_user_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListUserAccessTokensResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_user_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CreateUserAccessTokenRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_user_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CreateUserAccessTokenResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_user_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteUserAccessTokenRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_user_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteUserAccessTokenResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_v2_user_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UserAccessToken); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_v2_user_service_proto_rawDesc,
NumEnums: 1,
NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_api_v2_user_service_proto_goTypes,
DependencyIndexes: file_api_v2_user_service_proto_depIdxs,
EnumInfos: file_api_v2_user_service_proto_enumTypes,
MessageInfos: file_api_v2_user_service_proto_msgTypes,
}.Build()
File_api_v2_user_service_proto = out.File
file_api_v2_user_service_proto_rawDesc = nil
file_api_v2_user_service_proto_goTypes = nil
file_api_v2_user_service_proto_depIdxs = nil
}

View File

@ -0,0 +1,534 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: api/v2/user_service.proto
/*
Package apiv2 is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package apiv2
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
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) {
var protoReq GetUserRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := client.GetUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_GetUser_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetUserRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := server.GetUser(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_ListUserAccessTokens_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUserAccessTokensRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := client.ListUserAccessTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_ListUserAccessTokens_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUserAccessTokensRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := server.ListUserAccessTokens(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_CreateUserAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreateUserAccessTokenRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.UserAccessToken); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := client.CreateUserAccessToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_CreateUserAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreateUserAccessTokenRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.UserAccessToken); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
msg, err := server.CreateUserAccessToken(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_DeleteUserAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteUserAccessTokenRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
val, ok = pathParams["access_token"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "access_token")
}
protoReq.AccessToken, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "access_token", err)
}
msg, err := client.DeleteUserAccessToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_DeleteUserAccessToken_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteUserAccessTokenRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.Int32(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
val, ok = pathParams["access_token"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "access_token")
}
protoReq.AccessToken, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "access_token", err)
}
msg, err := server.DeleteUserAccessToken(ctx, &protoReq)
return msg, metadata, err
}
// RegisterUserServiceHandlerServer registers the http handlers for service UserService to "mux".
// UnaryRPC :call UserServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterUserServiceHandlerFromEndpoint instead.
func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server UserServiceServer) error {
mux.Handle("GET", pattern_UserService_GetUser_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/slash.api.v2.UserService/GetUser", runtime.WithHTTPPathPattern("/api/v2/users/{id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_GetUser_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_GetUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_UserService_ListUserAccessTokens_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/slash.api.v2.UserService/ListUserAccessTokens", runtime.WithHTTPPathPattern("/api/v2/users/{id}/access_tokens"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_ListUserAccessTokens_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_ListUserAccessTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_UserService_CreateUserAccessToken_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/slash.api.v2.UserService/CreateUserAccessToken", runtime.WithHTTPPathPattern("/api/v2/users/{id}/access_tokens"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_CreateUserAccessToken_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_CreateUserAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_UserService_DeleteUserAccessToken_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/slash.api.v2.UserService/DeleteUserAccessToken", runtime.WithHTTPPathPattern("/api/v2/users/{id}/access_tokens/{access_token}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_DeleteUserAccessToken_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_DeleteUserAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterUserServiceHandlerFromEndpoint is same as RegisterUserServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterUserServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterUserServiceHandler(ctx, mux, conn)
}
// RegisterUserServiceHandler registers the http handlers for service UserService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterUserServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterUserServiceHandlerClient(ctx, mux, NewUserServiceClient(conn))
}
// RegisterUserServiceHandlerClient registers the http handlers for service UserService
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "UserServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "UserServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "UserServiceClient" to call the correct interceptors.
func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client UserServiceClient) error {
mux.Handle("GET", pattern_UserService_GetUser_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/slash.api.v2.UserService/GetUser", runtime.WithHTTPPathPattern("/api/v2/users/{id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_GetUser_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_GetUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_UserService_ListUserAccessTokens_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/slash.api.v2.UserService/ListUserAccessTokens", runtime.WithHTTPPathPattern("/api/v2/users/{id}/access_tokens"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_ListUserAccessTokens_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_ListUserAccessTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_UserService_CreateUserAccessToken_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/slash.api.v2.UserService/CreateUserAccessToken", runtime.WithHTTPPathPattern("/api/v2/users/{id}/access_tokens"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_CreateUserAccessToken_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_CreateUserAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_UserService_DeleteUserAccessToken_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)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/slash.api.v2.UserService/DeleteUserAccessToken", runtime.WithHTTPPathPattern("/api/v2/users/{id}/access_tokens/{access_token}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_DeleteUserAccessToken_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_DeleteUserAccessToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "users", "id"}, ""))
pattern_UserService_ListUserAccessTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v2", "users", "id", "access_tokens"}, ""))
pattern_UserService_CreateUserAccessToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v2", "users", "id", "access_tokens"}, ""))
pattern_UserService_DeleteUserAccessToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"api", "v2", "users", "id", "access_tokens", "access_token"}, ""))
)
var (
forward_UserService_GetUser_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserAccessTokens_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUserAccessToken_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUserAccessToken_0 = runtime.ForwardResponseMessage
)

View File

@ -0,0 +1,228 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: api/v2/user_service.proto
package apiv2
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
UserService_GetUser_FullMethodName = "/slash.api.v2.UserService/GetUser"
UserService_ListUserAccessTokens_FullMethodName = "/slash.api.v2.UserService/ListUserAccessTokens"
UserService_CreateUserAccessToken_FullMethodName = "/slash.api.v2.UserService/CreateUserAccessToken"
UserService_DeleteUserAccessToken_FullMethodName = "/slash.api.v2.UserService/DeleteUserAccessToken"
)
// UserServiceClient is the client API for UserService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type UserServiceClient interface {
// GetUser returns a user by id.
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error)
// ListUserAccessTokens returns a list of access tokens for a user.
ListUserAccessTokens(ctx context.Context, in *ListUserAccessTokensRequest, opts ...grpc.CallOption) (*ListUserAccessTokensResponse, error)
// CreateUserAccessToken creates a new access token for a user.
CreateUserAccessToken(ctx context.Context, in *CreateUserAccessTokenRequest, opts ...grpc.CallOption) (*CreateUserAccessTokenResponse, error)
// DeleteUserAccessToken deletes an access token for a user.
DeleteUserAccessToken(ctx context.Context, in *DeleteUserAccessTokenRequest, opts ...grpc.CallOption) (*DeleteUserAccessTokenResponse, error)
}
type userServiceClient struct {
cc grpc.ClientConnInterface
}
func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
return &userServiceClient{cc}
}
func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) {
out := new(GetUserResponse)
err := c.cc.Invoke(ctx, UserService_GetUser_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) ListUserAccessTokens(ctx context.Context, in *ListUserAccessTokensRequest, opts ...grpc.CallOption) (*ListUserAccessTokensResponse, error) {
out := new(ListUserAccessTokensResponse)
err := c.cc.Invoke(ctx, UserService_ListUserAccessTokens_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) CreateUserAccessToken(ctx context.Context, in *CreateUserAccessTokenRequest, opts ...grpc.CallOption) (*CreateUserAccessTokenResponse, error) {
out := new(CreateUserAccessTokenResponse)
err := c.cc.Invoke(ctx, UserService_CreateUserAccessToken_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) DeleteUserAccessToken(ctx context.Context, in *DeleteUserAccessTokenRequest, opts ...grpc.CallOption) (*DeleteUserAccessTokenResponse, error) {
out := new(DeleteUserAccessTokenResponse)
err := c.cc.Invoke(ctx, UserService_DeleteUserAccessToken_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// UserServiceServer is the server API for UserService service.
// All implementations must embed UnimplementedUserServiceServer
// for forward compatibility
type UserServiceServer interface {
// GetUser returns a user by id.
GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)
// ListUserAccessTokens returns a list of access tokens for a user.
ListUserAccessTokens(context.Context, *ListUserAccessTokensRequest) (*ListUserAccessTokensResponse, error)
// CreateUserAccessToken creates a new access token for a user.
CreateUserAccessToken(context.Context, *CreateUserAccessTokenRequest) (*CreateUserAccessTokenResponse, error)
// DeleteUserAccessToken deletes an access token for a user.
DeleteUserAccessToken(context.Context, *DeleteUserAccessTokenRequest) (*DeleteUserAccessTokenResponse, error)
mustEmbedUnimplementedUserServiceServer()
}
// UnimplementedUserServiceServer must be embedded to have forward compatible implementations.
type UnimplementedUserServiceServer struct {
}
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
}
func (UnimplementedUserServiceServer) ListUserAccessTokens(context.Context, *ListUserAccessTokensRequest) (*ListUserAccessTokensResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUserAccessTokens not implemented")
}
func (UnimplementedUserServiceServer) CreateUserAccessToken(context.Context, *CreateUserAccessTokenRequest) (*CreateUserAccessTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateUserAccessToken not implemented")
}
func (UnimplementedUserServiceServer) DeleteUserAccessToken(context.Context, *DeleteUserAccessTokenRequest) (*DeleteUserAccessTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteUserAccessToken not implemented")
}
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
// UnsafeUserServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to UserServiceServer will
// result in compilation errors.
type UnsafeUserServiceServer interface {
mustEmbedUnimplementedUserServiceServer()
}
func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
s.RegisterService(&UserService_ServiceDesc, srv)
}
func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).GetUser(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_GetUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).GetUser(ctx, req.(*GetUserRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_ListUserAccessTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListUserAccessTokensRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).ListUserAccessTokens(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_ListUserAccessTokens_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).ListUserAccessTokens(ctx, req.(*ListUserAccessTokensRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_CreateUserAccessToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateUserAccessTokenRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).CreateUserAccessToken(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_CreateUserAccessToken_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).CreateUserAccessToken(ctx, req.(*CreateUserAccessTokenRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_DeleteUserAccessToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteUserAccessTokenRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).DeleteUserAccessToken(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_DeleteUserAccessToken_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).DeleteUserAccessToken(ctx, req.(*DeleteUserAccessTokenRequest))
}
return interceptor(ctx, in, info, handler)
}
// UserService_ServiceDesc is the grpc.ServiceDesc for UserService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var UserService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "slash.api.v2.UserService",
HandlerType: (*UserServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetUser",
Handler: _UserService_GetUser_Handler,
},
{
MethodName: "ListUserAccessTokens",
Handler: _UserService_ListUserAccessTokens_Handler,
},
{
MethodName: "CreateUserAccessToken",
Handler: _UserService_CreateUserAccessToken_Handler,
},
{
MethodName: "DeleteUserAccessToken",
Handler: _UserService_DeleteUserAccessToken_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/v2/user_service.proto",
}

223
proto/gen/store/README.md Normal file
View File

@ -0,0 +1,223 @@
# Protocol Documentation
<a name="top"></a>
## Table of Contents
- [store/common.proto](#store_common-proto)
- [RowStatus](#slash-store-RowStatus)
- [store/shortcut.proto](#store_shortcut-proto)
- [OpenGraphMetadata](#slash-store-OpenGraphMetadata)
- [Shortcut](#slash-store-Shortcut)
- [Visibility](#slash-store-Visibility)
- [store/user_setting.proto](#store_user_setting-proto)
- [AccessTokensUserSetting](#slash-store-AccessTokensUserSetting)
- [AccessTokensUserSetting.AccessToken](#slash-store-AccessTokensUserSetting-AccessToken)
- [UserSetting](#slash-store-UserSetting)
- [UserSettingKey](#slash-store-UserSettingKey)
- [Scalar Value Types](#scalar-value-types)
<a name="store_common-proto"></a>
<p align="right"><a href="#top">Top</a></p>
## store/common.proto
<a name="slash-store-RowStatus"></a>
### RowStatus
| Name | Number | Description |
| ---- | ------ | ----------- |
| ROW_STATUS_UNSPECIFIED | 0 | |
| NORMAL | 1 | |
| ARCHIVED | 2 | |
<a name="store_shortcut-proto"></a>
<p align="right"><a href="#top">Top</a></p>
## store/shortcut.proto
<a name="slash-store-OpenGraphMetadata"></a>
### OpenGraphMetadata
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| title | [string](#string) | | |
| description | [string](#string) | | |
| image | [string](#string) | | |
<a name="slash-store-Shortcut"></a>
### Shortcut
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | |
| creator_id | [int32](#int32) | | |
| created_ts | [int64](#int64) | | |
| updated_ts | [int64](#int64) | | |
| row_status | [RowStatus](#slash-store-RowStatus) | | |
| name | [string](#string) | | |
| link | [string](#string) | | |
| title | [string](#string) | | |
| tags | [string](#string) | repeated | |
| description | [string](#string) | | |
| visibility | [Visibility](#slash-store-Visibility) | | |
| og_metadata | [OpenGraphMetadata](#slash-store-OpenGraphMetadata) | | |
<a name="slash-store-Visibility"></a>
### Visibility
| Name | Number | Description |
| ---- | ------ | ----------- |
| VISIBILITY_UNSPECIFIED | 0 | |
| PRIVATE | 1 | |
| WORKSPACE | 2 | |
| PUBLIC | 3 | |
<a name="store_user_setting-proto"></a>
<p align="right"><a href="#top">Top</a></p>
## store/user_setting.proto
<a name="slash-store-AccessTokensUserSetting"></a>
### AccessTokensUserSetting
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| access_tokens | [AccessTokensUserSetting.AccessToken](#slash-store-AccessTokensUserSetting-AccessToken) | repeated | |
<a name="slash-store-AccessTokensUserSetting-AccessToken"></a>
### AccessTokensUserSetting.AccessToken
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| access_token | [string](#string) | | |
| description | [string](#string) | | |
<a name="slash-store-UserSetting"></a>
### UserSetting
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| user_id | [int32](#int32) | | |
| key | [UserSettingKey](#slash-store-UserSettingKey) | | |
| access_tokens_user_setting | [AccessTokensUserSetting](#slash-store-AccessTokensUserSetting) | | |
<a name="slash-store-UserSettingKey"></a>
### UserSettingKey
| Name | Number | Description |
| ---- | ------ | ----------- |
| USER_SETTING_KEY_UNSPECIFIED | 0 | |
| USER_SETTING_ACCESS_TOKENS | 1 | |
## Scalar Value Types
| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- |
| <a name="double" /> double | | double | double | float | float64 | double | float | Float |
| <a name="float" /> float | | float | float | float | float32 | float | float | Float |
| <a name="int32" /> int32 | Uses variable-length encoding. Inefficient for encoding negative numbers if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| <a name="int64" /> int64 | Uses variable-length encoding. Inefficient for encoding negative numbers if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| <a name="uint32" /> uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |
| <a name="uint64" /> uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |
| <a name="sint32" /> sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| <a name="sint64" /> sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| <a name="fixed32" /> fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |
| <a name="fixed64" /> fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |
| <a name="sfixed32" /> sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| <a name="sfixed64" /> sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| <a name="bool" /> bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass |
| <a name="string" /> string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| <a name="bytes" /> bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |

View File

@ -0,0 +1,141 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: store/common.proto
package store
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type RowStatus int32
const (
RowStatus_ROW_STATUS_UNSPECIFIED RowStatus = 0
RowStatus_NORMAL RowStatus = 1
RowStatus_ARCHIVED RowStatus = 2
)
// Enum value maps for RowStatus.
var (
RowStatus_name = map[int32]string{
0: "ROW_STATUS_UNSPECIFIED",
1: "NORMAL",
2: "ARCHIVED",
}
RowStatus_value = map[string]int32{
"ROW_STATUS_UNSPECIFIED": 0,
"NORMAL": 1,
"ARCHIVED": 2,
}
)
func (x RowStatus) Enum() *RowStatus {
p := new(RowStatus)
*p = x
return p
}
func (x RowStatus) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (RowStatus) Descriptor() protoreflect.EnumDescriptor {
return file_store_common_proto_enumTypes[0].Descriptor()
}
func (RowStatus) Type() protoreflect.EnumType {
return &file_store_common_proto_enumTypes[0]
}
func (x RowStatus) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use RowStatus.Descriptor instead.
func (RowStatus) EnumDescriptor() ([]byte, []int) {
return file_store_common_proto_rawDescGZIP(), []int{0}
}
var File_store_common_proto protoreflect.FileDescriptor
var file_store_common_proto_rawDesc = []byte{
0x0a, 0x12, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72,
0x65, 0x2a, 0x41, 0x0a, 0x09, 0x52, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a,
0x0a, 0x16, 0x52, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53,
0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f,
0x52, 0x4d, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x52, 0x43, 0x48, 0x49, 0x56,
0x45, 0x44, 0x10, 0x02, 0x42, 0x95, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x6c, 0x61,
0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x6a, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x6c, 0x61, 0x73,
0x68, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72,
0x65, 0xa2, 0x02, 0x03, 0x53, 0x53, 0x58, 0xaa, 0x02, 0x0b, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x2e,
0x53, 0x74, 0x6f, 0x72, 0x65, 0xca, 0x02, 0x0b, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x53, 0x74,
0x6f, 0x72, 0x65, 0xe2, 0x02, 0x17, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x53, 0x74, 0x6f, 0x72,
0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c,
0x53, 0x6c, 0x61, 0x73, 0x68, 0x3a, 0x3a, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_store_common_proto_rawDescOnce sync.Once
file_store_common_proto_rawDescData = file_store_common_proto_rawDesc
)
func file_store_common_proto_rawDescGZIP() []byte {
file_store_common_proto_rawDescOnce.Do(func() {
file_store_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_store_common_proto_rawDescData)
})
return file_store_common_proto_rawDescData
}
var file_store_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_store_common_proto_goTypes = []interface{}{
(RowStatus)(0), // 0: slash.store.RowStatus
}
var file_store_common_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_store_common_proto_init() }
func file_store_common_proto_init() {
if File_store_common_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_store_common_proto_rawDesc,
NumEnums: 1,
NumMessages: 0,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_store_common_proto_goTypes,
DependencyIndexes: file_store_common_proto_depIdxs,
EnumInfos: file_store_common_proto_enumTypes,
}.Build()
File_store_common_proto = out.File
file_store_common_proto_rawDesc = nil
file_store_common_proto_goTypes = nil
file_store_common_proto_depIdxs = nil
}

View File

@ -0,0 +1,411 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: store/shortcut.proto
package store
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Visibility int32
const (
Visibility_VISIBILITY_UNSPECIFIED Visibility = 0
Visibility_PRIVATE Visibility = 1
Visibility_WORKSPACE Visibility = 2
Visibility_PUBLIC Visibility = 3
)
// Enum value maps for Visibility.
var (
Visibility_name = map[int32]string{
0: "VISIBILITY_UNSPECIFIED",
1: "PRIVATE",
2: "WORKSPACE",
3: "PUBLIC",
}
Visibility_value = map[string]int32{
"VISIBILITY_UNSPECIFIED": 0,
"PRIVATE": 1,
"WORKSPACE": 2,
"PUBLIC": 3,
}
)
func (x Visibility) Enum() *Visibility {
p := new(Visibility)
*p = x
return p
}
func (x Visibility) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Visibility) Descriptor() protoreflect.EnumDescriptor {
return file_store_shortcut_proto_enumTypes[0].Descriptor()
}
func (Visibility) Type() protoreflect.EnumType {
return &file_store_shortcut_proto_enumTypes[0]
}
func (x Visibility) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Visibility.Descriptor instead.
func (Visibility) EnumDescriptor() ([]byte, []int) {
return file_store_shortcut_proto_rawDescGZIP(), []int{0}
}
type Shortcut struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
CreatorId int32 `protobuf:"varint,2,opt,name=creator_id,json=creatorId,proto3" json:"creator_id,omitempty"`
CreatedTs int64 `protobuf:"varint,3,opt,name=created_ts,json=createdTs,proto3" json:"created_ts,omitempty"`
UpdatedTs int64 `protobuf:"varint,4,opt,name=updated_ts,json=updatedTs,proto3" json:"updated_ts,omitempty"`
RowStatus RowStatus `protobuf:"varint,5,opt,name=row_status,json=rowStatus,proto3,enum=slash.store.RowStatus" json:"row_status,omitempty"`
Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
Link string `protobuf:"bytes,7,opt,name=link,proto3" json:"link,omitempty"`
Title string `protobuf:"bytes,8,opt,name=title,proto3" json:"title,omitempty"`
Tags []string `protobuf:"bytes,9,rep,name=tags,proto3" json:"tags,omitempty"`
Description string `protobuf:"bytes,10,opt,name=description,proto3" json:"description,omitempty"`
Visibility Visibility `protobuf:"varint,11,opt,name=visibility,proto3,enum=slash.store.Visibility" json:"visibility,omitempty"`
OgMetadata *OpenGraphMetadata `protobuf:"bytes,12,opt,name=og_metadata,json=ogMetadata,proto3" json:"og_metadata,omitempty"`
}
func (x *Shortcut) Reset() {
*x = Shortcut{}
if protoimpl.UnsafeEnabled {
mi := &file_store_shortcut_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Shortcut) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Shortcut) ProtoMessage() {}
func (x *Shortcut) ProtoReflect() protoreflect.Message {
mi := &file_store_shortcut_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Shortcut.ProtoReflect.Descriptor instead.
func (*Shortcut) Descriptor() ([]byte, []int) {
return file_store_shortcut_proto_rawDescGZIP(), []int{0}
}
func (x *Shortcut) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *Shortcut) GetCreatorId() int32 {
if x != nil {
return x.CreatorId
}
return 0
}
func (x *Shortcut) GetCreatedTs() int64 {
if x != nil {
return x.CreatedTs
}
return 0
}
func (x *Shortcut) GetUpdatedTs() int64 {
if x != nil {
return x.UpdatedTs
}
return 0
}
func (x *Shortcut) GetRowStatus() RowStatus {
if x != nil {
return x.RowStatus
}
return RowStatus_ROW_STATUS_UNSPECIFIED
}
func (x *Shortcut) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Shortcut) GetLink() string {
if x != nil {
return x.Link
}
return ""
}
func (x *Shortcut) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
func (x *Shortcut) GetTags() []string {
if x != nil {
return x.Tags
}
return nil
}
func (x *Shortcut) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *Shortcut) GetVisibility() Visibility {
if x != nil {
return x.Visibility
}
return Visibility_VISIBILITY_UNSPECIFIED
}
func (x *Shortcut) GetOgMetadata() *OpenGraphMetadata {
if x != nil {
return x.OgMetadata
}
return nil
}
type OpenGraphMetadata struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
Image string `protobuf:"bytes,3,opt,name=image,proto3" json:"image,omitempty"`
}
func (x *OpenGraphMetadata) Reset() {
*x = OpenGraphMetadata{}
if protoimpl.UnsafeEnabled {
mi := &file_store_shortcut_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OpenGraphMetadata) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OpenGraphMetadata) ProtoMessage() {}
func (x *OpenGraphMetadata) ProtoReflect() protoreflect.Message {
mi := &file_store_shortcut_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use OpenGraphMetadata.ProtoReflect.Descriptor instead.
func (*OpenGraphMetadata) Descriptor() ([]byte, []int) {
return file_store_shortcut_proto_rawDescGZIP(), []int{1}
}
func (x *OpenGraphMetadata) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
func (x *OpenGraphMetadata) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *OpenGraphMetadata) GetImage() string {
if x != nil {
return x.Image
}
return ""
}
var File_store_shortcut_proto protoreflect.FileDescriptor
var file_store_shortcut_proto_rawDesc = []byte{
0x0a, 0x14, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x1a, 0x12, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9c, 0x03, 0x0a, 0x08, 0x53, 0x68, 0x6f, 0x72,
0x74, 0x63, 0x75, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f,
0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74,
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
0x54, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x73,
0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x54,
0x73, 0x12, 0x35, 0x0a, 0x0a, 0x72, 0x6f, 0x77, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x2e, 0x52, 0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x09, 0x72,
0x6f, 0x77, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04,
0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b,
0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x09,
0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x0a,
0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x17, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x56,
0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62,
0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0b, 0x6f, 0x67, 0x5f, 0x6d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x6c, 0x61,
0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x47, 0x72, 0x61,
0x70, 0x68, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0a, 0x6f, 0x67, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x61, 0x0a, 0x11, 0x4f, 0x70, 0x65, 0x6e, 0x47, 0x72,
0x61, 0x70, 0x68, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x74,
0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c,
0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2a, 0x50, 0x0a, 0x0a, 0x56, 0x69, 0x73,
0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x49, 0x53, 0x49, 0x42,
0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10, 0x01,
0x12, 0x0d, 0x0a, 0x09, 0x57, 0x4f, 0x52, 0x4b, 0x53, 0x50, 0x41, 0x43, 0x45, 0x10, 0x02, 0x12,
0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x03, 0x42, 0x97, 0x01, 0x0a, 0x0f,
0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42,
0x0d, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01,
0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f,
0x6a, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0xa2, 0x02, 0x03, 0x53, 0x53, 0x58,
0xaa, 0x02, 0x0b, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xca, 0x02,
0x0b, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xe2, 0x02, 0x17, 0x53,
0x6c, 0x61, 0x73, 0x68, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x3a, 0x3a,
0x53, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_store_shortcut_proto_rawDescOnce sync.Once
file_store_shortcut_proto_rawDescData = file_store_shortcut_proto_rawDesc
)
func file_store_shortcut_proto_rawDescGZIP() []byte {
file_store_shortcut_proto_rawDescOnce.Do(func() {
file_store_shortcut_proto_rawDescData = protoimpl.X.CompressGZIP(file_store_shortcut_proto_rawDescData)
})
return file_store_shortcut_proto_rawDescData
}
var file_store_shortcut_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_store_shortcut_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_store_shortcut_proto_goTypes = []interface{}{
(Visibility)(0), // 0: slash.store.Visibility
(*Shortcut)(nil), // 1: slash.store.Shortcut
(*OpenGraphMetadata)(nil), // 2: slash.store.OpenGraphMetadata
(RowStatus)(0), // 3: slash.store.RowStatus
}
var file_store_shortcut_proto_depIdxs = []int32{
3, // 0: slash.store.Shortcut.row_status:type_name -> slash.store.RowStatus
0, // 1: slash.store.Shortcut.visibility:type_name -> slash.store.Visibility
2, // 2: slash.store.Shortcut.og_metadata:type_name -> slash.store.OpenGraphMetadata
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension 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_shortcut_proto_init() }
func file_store_shortcut_proto_init() {
if File_store_shortcut_proto != nil {
return
}
file_store_common_proto_init()
if !protoimpl.UnsafeEnabled {
file_store_shortcut_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Shortcut); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_store_shortcut_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OpenGraphMetadata); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_store_shortcut_proto_rawDesc,
NumEnums: 1,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_store_shortcut_proto_goTypes,
DependencyIndexes: file_store_shortcut_proto_depIdxs,
EnumInfos: file_store_shortcut_proto_enumTypes,
MessageInfos: file_store_shortcut_proto_msgTypes,
}.Build()
File_store_shortcut_proto = out.File
file_store_shortcut_proto_rawDesc = nil
file_store_shortcut_proto_goTypes = nil
file_store_shortcut_proto_depIdxs = nil
}

View File

@ -0,0 +1,398 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: store/user_setting.proto
package store
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type UserSettingKey int32
const (
UserSettingKey_USER_SETTING_KEY_UNSPECIFIED UserSettingKey = 0
UserSettingKey_USER_SETTING_ACCESS_TOKENS UserSettingKey = 1
)
// Enum value maps for UserSettingKey.
var (
UserSettingKey_name = map[int32]string{
0: "USER_SETTING_KEY_UNSPECIFIED",
1: "USER_SETTING_ACCESS_TOKENS",
}
UserSettingKey_value = map[string]int32{
"USER_SETTING_KEY_UNSPECIFIED": 0,
"USER_SETTING_ACCESS_TOKENS": 1,
}
)
func (x UserSettingKey) Enum() *UserSettingKey {
p := new(UserSettingKey)
*p = x
return p
}
func (x UserSettingKey) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (UserSettingKey) Descriptor() protoreflect.EnumDescriptor {
return file_store_user_setting_proto_enumTypes[0].Descriptor()
}
func (UserSettingKey) Type() protoreflect.EnumType {
return &file_store_user_setting_proto_enumTypes[0]
}
func (x UserSettingKey) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use UserSettingKey.Descriptor instead.
func (UserSettingKey) EnumDescriptor() ([]byte, []int) {
return file_store_user_setting_proto_rawDescGZIP(), []int{0}
}
type UserSetting struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UserId int32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Key UserSettingKey `protobuf:"varint,2,opt,name=key,proto3,enum=slash.store.UserSettingKey" json:"key,omitempty"`
// Types that are assignable to Value:
//
// *UserSetting_AccessTokensUserSetting
Value isUserSetting_Value `protobuf_oneof:"value"`
}
func (x *UserSetting) Reset() {
*x = UserSetting{}
if protoimpl.UnsafeEnabled {
mi := &file_store_user_setting_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UserSetting) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserSetting) ProtoMessage() {}
func (x *UserSetting) ProtoReflect() protoreflect.Message {
mi := &file_store_user_setting_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserSetting.ProtoReflect.Descriptor instead.
func (*UserSetting) Descriptor() ([]byte, []int) {
return file_store_user_setting_proto_rawDescGZIP(), []int{0}
}
func (x *UserSetting) GetUserId() int32 {
if x != nil {
return x.UserId
}
return 0
}
func (x *UserSetting) GetKey() UserSettingKey {
if x != nil {
return x.Key
}
return UserSettingKey_USER_SETTING_KEY_UNSPECIFIED
}
func (m *UserSetting) GetValue() isUserSetting_Value {
if m != nil {
return m.Value
}
return nil
}
func (x *UserSetting) GetAccessTokensUserSetting() *AccessTokensUserSetting {
if x, ok := x.GetValue().(*UserSetting_AccessTokensUserSetting); ok {
return x.AccessTokensUserSetting
}
return nil
}
type isUserSetting_Value interface {
isUserSetting_Value()
}
type UserSetting_AccessTokensUserSetting struct {
AccessTokensUserSetting *AccessTokensUserSetting `protobuf:"bytes,3,opt,name=access_tokens_user_setting,json=accessTokensUserSetting,proto3,oneof"`
}
func (*UserSetting_AccessTokensUserSetting) isUserSetting_Value() {}
type AccessTokensUserSetting struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AccessTokens []*AccessTokensUserSetting_AccessToken `protobuf:"bytes,1,rep,name=access_tokens,json=accessTokens,proto3" json:"access_tokens,omitempty"`
}
func (x *AccessTokensUserSetting) Reset() {
*x = AccessTokensUserSetting{}
if protoimpl.UnsafeEnabled {
mi := &file_store_user_setting_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AccessTokensUserSetting) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AccessTokensUserSetting) ProtoMessage() {}
func (x *AccessTokensUserSetting) ProtoReflect() protoreflect.Message {
mi := &file_store_user_setting_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AccessTokensUserSetting.ProtoReflect.Descriptor instead.
func (*AccessTokensUserSetting) Descriptor() ([]byte, []int) {
return file_store_user_setting_proto_rawDescGZIP(), []int{1}
}
func (x *AccessTokensUserSetting) GetAccessTokens() []*AccessTokensUserSetting_AccessToken {
if x != nil {
return x.AccessTokens
}
return nil
}
type AccessTokensUserSetting_AccessToken struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"`
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
}
func (x *AccessTokensUserSetting_AccessToken) Reset() {
*x = AccessTokensUserSetting_AccessToken{}
if protoimpl.UnsafeEnabled {
mi := &file_store_user_setting_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AccessTokensUserSetting_AccessToken) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AccessTokensUserSetting_AccessToken) ProtoMessage() {}
func (x *AccessTokensUserSetting_AccessToken) ProtoReflect() protoreflect.Message {
mi := &file_store_user_setting_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AccessTokensUserSetting_AccessToken.ProtoReflect.Descriptor instead.
func (*AccessTokensUserSetting_AccessToken) Descriptor() ([]byte, []int) {
return file_store_user_setting_proto_rawDescGZIP(), []int{1, 0}
}
func (x *AccessTokensUserSetting_AccessToken) GetAccessToken() string {
if x != nil {
return x.AccessToken
}
return ""
}
func (x *AccessTokensUserSetting_AccessToken) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
var File_store_user_setting_proto protoreflect.FileDescriptor
var file_store_user_setting_proto_rawDesc = []byte{
0x0a, 0x18, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74,
0x74, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x73, 0x6c, 0x61, 0x73,
0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x22, 0xc3, 0x01, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72,
0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64,
0x12, 0x2d, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e,
0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72,
0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x63, 0x0a, 0x1a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73,
0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72,
0x65, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x55, 0x73,
0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x17, 0x61, 0x63, 0x63,
0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74,
0x74, 0x69, 0x6e, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xc4, 0x01,
0x0a, 0x17, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x55, 0x73,
0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x55, 0x0a, 0x0d, 0x61, 0x63, 0x63,
0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x30, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41,
0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x55, 0x73, 0x65, 0x72, 0x53,
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b,
0x65, 0x6e, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73,
0x1a, 0x52, 0x0a, 0x0b, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12,
0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b,
0x65, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x52, 0x0a, 0x0e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74,
0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x1c, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53,
0x45, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x53, 0x45, 0x52,
0x5f, 0x53, 0x45, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f,
0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x53, 0x10, 0x01, 0x42, 0x9a, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d,
0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x10, 0x55, 0x73,
0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01,
0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f,
0x6a, 0x61, 0x63, 0x6b, 0x2f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0xa2, 0x02, 0x03, 0x53, 0x53, 0x58,
0xaa, 0x02, 0x0b, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xca, 0x02,
0x0b, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0xe2, 0x02, 0x17, 0x53,
0x6c, 0x61, 0x73, 0x68, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x3a, 0x3a,
0x53, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_store_user_setting_proto_rawDescOnce sync.Once
file_store_user_setting_proto_rawDescData = file_store_user_setting_proto_rawDesc
)
func file_store_user_setting_proto_rawDescGZIP() []byte {
file_store_user_setting_proto_rawDescOnce.Do(func() {
file_store_user_setting_proto_rawDescData = protoimpl.X.CompressGZIP(file_store_user_setting_proto_rawDescData)
})
return file_store_user_setting_proto_rawDescData
}
var file_store_user_setting_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_store_user_setting_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_store_user_setting_proto_goTypes = []interface{}{
(UserSettingKey)(0), // 0: slash.store.UserSettingKey
(*UserSetting)(nil), // 1: slash.store.UserSetting
(*AccessTokensUserSetting)(nil), // 2: slash.store.AccessTokensUserSetting
(*AccessTokensUserSetting_AccessToken)(nil), // 3: slash.store.AccessTokensUserSetting.AccessToken
}
var file_store_user_setting_proto_depIdxs = []int32{
0, // 0: slash.store.UserSetting.key:type_name -> slash.store.UserSettingKey
2, // 1: slash.store.UserSetting.access_tokens_user_setting:type_name -> slash.store.AccessTokensUserSetting
3, // 2: slash.store.AccessTokensUserSetting.access_tokens:type_name -> slash.store.AccessTokensUserSetting.AccessToken
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension 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_user_setting_proto_init() }
func file_store_user_setting_proto_init() {
if File_store_user_setting_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_store_user_setting_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UserSetting); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_store_user_setting_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AccessTokensUserSetting); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_store_user_setting_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AccessTokensUserSetting_AccessToken); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_store_user_setting_proto_msgTypes[0].OneofWrappers = []interface{}{
(*UserSetting_AccessTokensUserSetting)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_store_user_setting_proto_rawDesc,
NumEnums: 1,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_store_user_setting_proto_goTypes,
DependencyIndexes: file_store_user_setting_proto_depIdxs,
EnumInfos: file_store_user_setting_proto_enumTypes,
MessageInfos: file_store_user_setting_proto_msgTypes,
}.Build()
File_store_user_setting_proto = out.File
file_store_user_setting_proto_rawDesc = nil
file_store_user_setting_proto_goTypes = nil
file_store_user_setting_proto_depIdxs = nil
}

13
proto/store/common.proto Normal file
View File

@ -0,0 +1,13 @@
syntax = "proto3";
package slash.store;
option go_package = "gen/store";
enum RowStatus {
ROW_STATUS_UNSPECIFIED = 0;
NORMAL = 1;
ARCHIVED = 2;
}

View File

@ -0,0 +1,51 @@
syntax = "proto3";
package slash.store;
import "store/common.proto";
option go_package = "gen/store";
message Shortcut {
int32 id = 1;
int32 creator_id = 2;
int64 created_ts = 3;
int64 updated_ts = 4;
RowStatus row_status = 5;
string name = 6;
string link = 7;
string title = 8;
repeated string tags = 9;
string description = 10;
Visibility visibility = 11;
OpenGraphMetadata og_metadata = 12;
}
message OpenGraphMetadata {
string title = 1;
string description = 2;
string image = 3;
}
enum Visibility {
VISIBILITY_UNSPECIFIED = 0;
PRIVATE = 1;
WORKSPACE = 2;
PUBLIC = 3;
}

View File

@ -0,0 +1,29 @@
syntax = "proto3";
package slash.store;
option go_package = "gen/store";
message UserSetting {
int32 user_id = 1;
UserSettingKey key = 2;
oneof value {
AccessTokensUserSetting access_tokens_user_setting = 3;
}
}
enum UserSettingKey {
USER_SETTING_KEY_UNSPECIFIED = 0;
USER_SETTING_ACCESS_TOKENS = 1;
}
message AccessTokensUserSetting {
message AccessToken {
string access_token = 1;
string description = 2;
}
repeated AccessToken access_tokens = 1;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
resources/demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 KiB

View File

@ -2,10 +2,10 @@ root = "."
tmp_dir = ".air"
[build]
bin = "./.air/shortify"
cmd = "go build -o ./.air/shortify ./cmd/shortify/main.go"
bin = "./.air/slash --mode dev"
cmd = "go build -o ./.air/slash ./cmd/slash/main.go"
delay = 1000
exclude_dir = [".air", "web", "build"]
exclude_dir = [".air", "web", "extension", "build"]
exclude_file = []
exclude_regex = []
exclude_unchanged = false

View File

@ -1,13 +0,0 @@
#!/bin/bash
# Usage: ./scripts/build.sh
set -e
cd "$(dirname "$0")/../"
echo "Start building backend..."
go build -o ./build/shortify ./cmd/shortify/main.go
echo "Backend built!"

View File

@ -1,9 +0,0 @@
#!/bin/bash
# Usage: ./scripts/start.sh
set -e
cd "$(dirname "$0")/../"
air -c ./scripts/.air.toml

View File

@ -1,133 +0,0 @@
package auth
import (
"net/http"
"strconv"
"time"
"github.com/boojack/shortify/store"
"github.com/golang-jwt/jwt/v4"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
)
const (
issuer = "shortify"
// Signing key section. For now, this is only used for signing, not for verifying since we only
// have 1 version. But it will be used to maintain backward compatibility if we change the signing mechanism.
keyID = "v1"
// AccessTokenAudienceName is the audience name of the access token.
AccessTokenAudienceName = "user.access-token"
// RefreshTokenAudienceName is the audience name of the refresh token.
RefreshTokenAudienceName = "user.refresh-token"
apiTokenDuration = 2 * time.Hour
accessTokenDuration = 24 * time.Hour
refreshTokenDuration = 7 * 24 * time.Hour
// RefreshThresholdDuration is the threshold duration for refreshing token.
RefreshThresholdDuration = 1 * time.Hour
// CookieExpDuration expires slightly earlier than the jwt expiration. Client would be logged out if the user
// cookie expires, thus the client would always logout first before attempting to make a request with the expired jwt.
// Suppose we have a valid refresh token, we will refresh the token in 2 cases:
// 1. The access token is about to expire in <<refreshThresholdDuration>>
// 2. The access token has already expired, we refresh the token so that the ongoing request can pass through.
CookieExpDuration = refreshTokenDuration - 1*time.Minute
// AccessTokenCookieName is the cookie name of access token.
AccessTokenCookieName = "access-token"
// RefreshTokenCookieName is the cookie name of refresh token.
RefreshTokenCookieName = "refresh-token"
// UserIDCookieName is the cookie name of user ID.
UserIDCookieName = "user"
)
type claimsMessage struct {
Name string `json:"name"`
jwt.RegisteredClaims
}
// GenerateAPIToken generates an API token.
func GenerateAPIToken(username string, userID int, secret string) (string, error) {
expirationTime := time.Now().Add(apiTokenDuration)
return generateToken(username, userID, AccessTokenAudienceName, expirationTime, []byte(secret))
}
// GenerateAccessToken generates an access token for web.
func GenerateAccessToken(username string, userID int, secret string) (string, error) {
expirationTime := time.Now().Add(accessTokenDuration)
return generateToken(username, userID, AccessTokenAudienceName, expirationTime, []byte(secret))
}
// GenerateRefreshToken generates a refresh token for web.
func GenerateRefreshToken(username string, userID int, secret string) (string, error) {
expirationTime := time.Now().Add(refreshTokenDuration)
return generateToken(username, userID, RefreshTokenAudienceName, expirationTime, []byte(secret))
}
// GenerateTokensAndSetCookies generates jwt token and saves it to the http-only cookie.
func GenerateTokensAndSetCookies(c echo.Context, user *store.User, secret string) error {
accessToken, err := GenerateAccessToken(user.Email, user.ID, secret)
if err != nil {
return errors.Wrap(err, "failed to generate access token")
}
cookieExp := time.Now().Add(CookieExpDuration)
setTokenCookie(c, AccessTokenCookieName, accessToken, cookieExp)
// We generate here a new refresh token and saving it to the cookie.
refreshToken, err := GenerateRefreshToken(user.Email, user.ID, secret)
if err != nil {
return errors.Wrap(err, "failed to generate refresh token")
}
setTokenCookie(c, RefreshTokenCookieName, refreshToken, cookieExp)
return nil
}
// RemoveTokensAndCookies removes the jwt token and refresh token from the cookies.
func RemoveTokensAndCookies(c echo.Context) {
// We set the expiration time to the past, so that the cookie will be removed.
cookieExp := time.Now().Add(-1 * time.Hour)
setTokenCookie(c, AccessTokenCookieName, "", cookieExp)
setTokenCookie(c, RefreshTokenCookieName, "", cookieExp)
}
// setTokenCookie sets the token to the cookie.
func setTokenCookie(c echo.Context, name, token string, expiration time.Time) {
cookie := new(http.Cookie)
cookie.Name = name
cookie.Value = token
cookie.Expires = expiration
cookie.Path = "/"
// Http-only helps mitigate the risk of client side script accessing the protected cookie.
cookie.HttpOnly = true
cookie.SameSite = http.SameSiteStrictMode
c.SetCookie(cookie)
}
// generateToken generates a jwt token.
func generateToken(username string, userID int, aud string, expirationTime time.Time, secret []byte) (string, error) {
// Create the JWT claims, which includes the username and expiry time.
claims := &claimsMessage{
Name: username,
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{aud},
// In JWT, the expiry time is expressed as unix milliseconds.
ExpiresAt: jwt.NewNumericDate(expirationTime),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: issuer,
Subject: strconv.Itoa(userID),
},
}
// Declare the token with the HS256 algorithm used for signing, and the claims.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token.Header["kid"] = keyID
// Create the JWT string.
tokenString, err := token.SignedString(secret)
if err != nil {
return "", err
}
return tokenString, nil
}

Some files were not shown because too many files have changed in this diff Show More