69 Commits

Author SHA1 Message Date
8612715371 chore: upgrade version to v0.4.3 2023-08-22 23:05:04 +08:00
e91050c803 chore: add delete user button 2023-08-22 23:02:14 +08:00
ec2ec74e31 feat: impl delete shortcut apiv2 2023-08-22 22:47:39 +08:00
bfb640f201 chore: update dialog width in extension 2023-08-22 09:27:49 +08:00
34f8a97309 feat: impl delete user apiv2 2023-08-22 09:15:27 +08:00
1c58702716 chore: fix linter warning 2023-08-21 02:03:16 +08:00
bd31c19a15 chore: fix typo 2023-08-21 01:55:16 +08:00
7e0ada6161 chore: add list users api 2023-08-21 01:53:52 +08:00
b5d6036fcf chore: update logger 2023-08-21 01:48:20 +08:00
0fcee9baf2 chore: update font family 2023-08-21 01:33:09 +08:00
f6fefdb8e6 chore: update joy-ui version 2023-08-21 00:15:41 +08:00
0ec06423e5 chore: update compact mode switch 2023-08-21 00:15:32 +08:00
8f028e4054 chore: add empty tags check 2023-08-21 00:07:11 +08:00
ae3b632f53 chore: remove analytics dialog 2023-08-18 22:52:57 +08:00
bafb17015c chore: update part of i18n 2023-08-18 09:00:42 +08:00
d939bb8250 chore: update version to 0.4.2 2023-08-16 19:15:44 +08:00
946548b33a fix: access token checks 2023-08-16 18:55:11 +08:00
d97a7e736d chore: add extension link to readme 2023-08-16 09:18:54 +08:00
e5d5ba5cbc chore: update extension version 2023-08-16 08:54:51 +08:00
ce4232c9f5 fix: create shortcut dialog height 2023-08-15 22:24:26 +08:00
bc6a72561c chore: update resource path setting checks 2023-08-15 21:38:02 +08:00
b9e5e7f2af feat: add resource service 2023-08-15 00:02:40 +08:00
96ab5b226d chore: fix method name 2023-08-13 00:00:14 +08:00
9c6f85e938 feat: add logo to extension 2023-08-12 00:47:08 +08:00
f1e3eace1a feat: add omnibox to extension 2023-08-11 00:26:04 +08:00
6f26523a11 chore: update extension docs 2023-08-11 00:20:39 +08:00
304a29a18c chore: upgrade extension version 2023-08-10 22:34:44 +08:00
3e5fa5573e chore: update options initial state check 2023-08-10 22:30:11 +08:00
93ed3c81ff fix: max width 2023-08-10 22:29:54 +08:00
0efd495f56 feat: add create shortcut button 2023-08-10 22:23:22 +08:00
ae56f6df8c chore: fix create shortcut 2023-08-10 22:23:12 +08:00
df51720310 chore: add server test 2023-08-10 20:57:43 +08:00
1194099667 feat: add create shortcut api 2023-08-09 23:31:52 +08:00
e936aaced1 chore: add create user api 2023-08-09 23:20:01 +08:00
0ee999a30a chore: update extension manifest 2023-08-09 09:04:41 +08:00
1211136037 chore: update resources 2023-08-09 09:04:13 +08:00
73061034b2 chore: update readme 2023-08-08 23:58:15 +08:00
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
141 changed files with 18077 additions and 655 deletions

View File

@ -1,4 +1,4 @@
name: Test
name: Backend Test
on:
push:

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

View File

@ -1,4 +1,4 @@
name: Test
name: Frontend Test
on:
push:

View File

@ -17,6 +17,7 @@
- Create customizable `/s/` short links for any URL.
- Share short links privately or with teammates.
- View analytics on link traffic and sources.
- Easy access to your shortcuts with browser extension.
- Open source self-hosted solution.
## Deploy with Docker in seconds
@ -26,3 +27,15 @@ docker run -d --name slash -p 5231:5231 -v ~/.slash/:/var/opt/slash yourselfhost
```
Learn more in [Self-hosting Slash with Docker](https://github.com/boojack/slash/blob/main/docs/install.md).
## Browser Extension
Slash provides a browser extension to help you use your shortcuts in the search bar to go to the corresponding URL.
![browser-extension-example](./resources/browser-extension-example.png)
### Chromium based browsers
For Chromium based browsers(Chrome, Edge, Arc, ...), you can install the extension from the [Chrome Web Store](https://chrome.google.com/webstore/detail/slash/ebaiehmkammnacjadffpicipfckgeobg).
Learn more in [The Browser Extension of Slash](https://github.com/boojack/slash/blob/main/docs/install-browser-extension.md).

View File

@ -1,7 +1,10 @@
package auth
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v4"
)
const (
@ -16,9 +19,46 @@ const (
// 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 cases:
// 1. The access token has already expired, we refresh the token so that the ongoing request can pass through.
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,12 +1,17 @@
package v1
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"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"
)
@ -46,9 +51,16 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
return echo.NewHTTPError(http.StatusUnauthorized, "unmatched email and password")
}
if err := 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))
})
@ -95,10 +107,16 @@ 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 := 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))
})
@ -108,3 +126,46 @@ func (s *APIV1Service) registerAuthRoutes(g *echo.Group, secret string) {
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

@ -4,10 +4,10 @@ import (
"fmt"
"net/http"
"strings"
"time"
"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"
@ -20,76 +20,6 @@ const (
UserIDContextKey = "user-id"
)
type claimsMessage struct {
Name string `json:"name"`
jwt.RegisteredClaims
}
// GenerateAccessToken generates an access token for web.
func GenerateAccessToken(username string, userID int32, secret string) (string, error) {
expirationTime := time.Now().Add(auth.AccessTokenDuration)
return generateToken(username, userID, auth.AccessTokenAudienceName, 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(auth.CookieExpDuration)
setTokenCookie(c, auth.AccessTokenCookieName, accessToken, cookieExp)
return nil
}
// RemoveTokensAndCookies removes the jwt token and refresh 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)
}
// generateToken generates a jwt token.
func generateToken(username string, userID int32, 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: auth.Issuer,
Subject: fmt.Sprint(userID),
},
}
// Declare the token with the HS256 algorithm used for signing, and the claims.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token.Header["kid"] = auth.KeyID
// Create the JWT string.
tokenString, err := token.SignedString(secret)
if err != nil {
return "", err
}
return tokenString, nil
}
func extractTokenFromHeader(c echo.Context) (string, error) {
authHeader := c.Request().Header.Get("Authorization")
if authHeader == "" {
@ -127,9 +57,7 @@ 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 {
ctx := c.Request().Context()
path := c.Request().URL.Path
@ -143,13 +71,13 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e
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, "/s/*") && method == http.MethodGet {
if util.HasPrefixes(path, "/s/") && method == http.MethodGet {
return next(c)
}
return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
}
claims := &claimsMessage{}
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)
@ -161,23 +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 {
RemoveTokensAndCookies(c)
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))
}
// We either have a valid access token or we will attempt to generate new access token and refresh token
// 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 {
return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.").WithInternal(err)
}
accessTokens, err := s.Store.GetUserAccessTokens(ctx, userID)
if err != nil {
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 {
@ -192,3 +126,12 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e
return next(c)
}
}
func validateAccessToken(accessTokenString string, userAccessTokens []*storepb.AccessTokensUserSetting_AccessToken) bool {
for _, userAccessToken := range userAccessTokens {
if accessTokenString == userAccessToken.AccessToken {
return true
}
}
return false
}

View File

@ -90,7 +90,7 @@ 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, &storepb.Shortcut{
shortcut := &storepb.Shortcut{
CreatorId: userID,
Name: strings.ToLower(create.Name),
Link: create.Link,
@ -98,12 +98,16 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
Description: create.Description,
Visibility: convertVisibilityToStorepb(create.Visibility),
Tags: create.Tags,
OgMetadata: &storepb.OpenGraphMetadata{
OgMetadata: &storepb.OpenGraphMetadata{},
}
if create.OpenGraphMetadata != nil {
shortcut.OgMetadata = &storepb.OpenGraphMetadata{
Title: create.OpenGraphMetadata.Title,
Description: create.OpenGraphMetadata.Description,
Image: create.OpenGraphMetadata.Image,
},
})
}
}
shortcut, err := s.Store.CreateShortcut(ctx, shortcut)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to create shortcut, err: %s", err)).SetInternal(err)
}
@ -349,6 +353,8 @@ func convertVisibilityToStorepb(visibility Visibility) storepb.Visibility {
switch visibility {
case VisibilityPublic:
return storepb.Visibility_PUBLIC
case VisibilityWorkspace:
return storepb.Visibility_WORKSPACE
case VisibilityPrivate:
return storepb.Visibility_PRIVATE
default:

View File

@ -3,12 +3,11 @@ package v2
import (
"context"
"net/http"
"strconv"
"strings"
"time"
"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"
@ -18,18 +17,6 @@ import (
"google.golang.org/grpc/status"
)
var authenticationAllowlistMethods = map[string]bool{
"/memos.api.v2.UserService/GetUser": true,
}
// 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
@ -41,14 +28,14 @@ const (
// GRPCAuthInterceptor is the auth interceptor for gRPC server.
type GRPCAuthInterceptor struct {
store *store.Store
Store *store.Store
secret string
}
// NewGRPCAuthInterceptor returns a new API auth interceptor.
func NewGRPCAuthInterceptor(store *store.Store, secret string) *GRPCAuthInterceptor {
return &GRPCAuthInterceptor{
store: store,
Store: store,
secret: secret,
}
}
@ -59,18 +46,38 @@ func (in *GRPCAuthInterceptor) AuthenticationInterceptor(ctx context.Context, re
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "failed to parse metadata from incoming context")
}
accessTokenStr, err := getTokenFromMetadata(md)
accessToken, err := getTokenFromMetadata(md)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, err.Error())
return nil, status.Errorf(codes.Unauthenticated, "failed to get access token from metadata: %v", err)
}
userID, err := in.authenticate(ctx, accessTokenStr)
userID, err := in.authenticate(ctx, accessToken)
if err != nil {
if IsAuthenticationAllowed(serverInfo.FullMethod) {
if isUnauthorizeAllowedMethod(serverInfo.FullMethod) {
return handler(ctx, request)
}
return nil, err
}
user, err := in.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return nil, errors.Wrap(err, "failed to get user")
}
if user == nil {
return nil, status.Errorf(codes.Unauthenticated, "user ID %q not exists in the access token", userID)
}
if isOnlyForAdminAllowedMethod(serverInfo.FullMethod) && user.Role != store.RoleAdmin {
return nil, status.Errorf(codes.PermissionDenied, "user ID %q is not admin", userID)
}
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)
@ -81,7 +88,7 @@ func (in *GRPCAuthInterceptor) authenticate(ctx context.Context, accessTokenStr
if accessTokenStr == "" {
return 0, status.Errorf(codes.Unauthenticated, "access token not found")
}
claims := &claimsMessage{}
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)
@ -108,7 +115,7 @@ func (in *GRPCAuthInterceptor) authenticate(ctx context.Context, accessTokenStr
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{
user, err := in.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
@ -125,15 +132,16 @@ func (in *GRPCAuthInterceptor) authenticate(ctx context.Context, accessTokenStr
}
func getTokenFromMetadata(md metadata.MD) (string, error) {
// Try to get the token from the authorization header first.
authorizationHeaders := md.Get("Authorization")
if len(md.Get("Authorization")) > 0 {
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
}
// check the HTTP cookie
// 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{}
@ -155,40 +163,11 @@ func audienceContains(audience jwt.ClaimStrings, token string) bool {
return false
}
type claimsMessage struct {
Name string `json:"name"`
jwt.RegisteredClaims
}
// GenerateAccessToken generates an access token for web.
func GenerateAccessToken(username string, userID int, secret string) (string, error) {
expirationTime := time.Now().Add(auth.AccessTokenDuration)
return generateToken(username, userID, auth.AccessTokenAudienceName, expirationTime, []byte(secret))
}
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: auth.Issuer,
Subject: strconv.Itoa(userID),
},
func validateAccessToken(accessTokenString string, userAccessTokens []*storepb.AccessTokensUserSetting_AccessToken) bool {
for _, userAccessToken := range userAccessTokens {
if accessTokenString == userAccessToken.AccessToken {
return true
}
// Declare the token with the HS256 algorithm used for signing, and the claims.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token.Header["kid"] = auth.KeyID
// Create the JWT string.
tokenString, err := token.SignedString(secret)
if err != nil {
return "", err
}
return tokenString, nil
return false
}

23
api/v2/acl_config.go Normal file
View File

@ -0,0 +1,23 @@
package v2
import "strings"
var allowedMethodsWhenUnauthorized = map[string]bool{}
// isUnauthorizeAllowedMethod returns true if the method is allowed to be called when the user is not authorized.
func isUnauthorizeAllowedMethod(methodName string) bool {
if strings.HasPrefix(methodName, "/grpc.reflection") {
return true
}
return allowedMethodsWhenUnauthorized[methodName]
}
var allowedMethodsOnlyForAdmin = map[string]bool{
"/slash.api.v2.UserService/CreateUser": true,
"/slash.api.v2.UserService/DeleteUser": true,
}
// isOnlyForAdminAllowedMethod returns true if the method is allowed to be called only by admin.
func isOnlyForAdminAllowedMethod(methodName string) bool {
return allowedMethodsOnlyForAdmin[methodName]
}

184
api/v2/shortcut_service.go Normal file
View File

@ -0,0 +1,184 @@
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"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
)
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 (s *ShortcutService) CreateShortcut(ctx context.Context, request *apiv2pb.CreateShortcutRequest) (*apiv2pb.CreateShortcutResponse, error) {
userID := ctx.Value(UserIDContextKey).(int32)
shortcut := &storepb.Shortcut{
CreatorId: userID,
Name: request.Shortcut.Name,
Link: request.Shortcut.Link,
Title: request.Shortcut.Title,
Tags: request.Shortcut.Tags,
Description: request.Shortcut.Description,
Visibility: storepb.Visibility(request.Shortcut.Visibility),
OgMetadata: &storepb.OpenGraphMetadata{},
}
if request.Shortcut.OgMetadata != nil {
shortcut.OgMetadata = &storepb.OpenGraphMetadata{
Title: request.Shortcut.OgMetadata.Title,
Description: request.Shortcut.OgMetadata.Description,
Image: request.Shortcut.OgMetadata.Image,
}
}
shortcut, err := s.Store.CreateShortcut(ctx, shortcut)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create shortcut, err: %v", err)
}
if err := s.createShortcutCreateActivity(ctx, shortcut); err != nil {
return nil, status.Errorf(codes.Internal, "failed to create activity, err: %v", err)
}
response := &apiv2pb.CreateShortcutResponse{
Shortcut: convertShortcutFromStorepb(shortcut),
}
return response, nil
}
func (s *ShortcutService) DeleteShortcut(ctx context.Context, request *apiv2pb.DeleteShortcutRequest) (*apiv2pb.DeleteShortcutResponse, error) {
userID := ctx.Value(UserIDContextKey).(int32)
currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user, err: %v", err)
}
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")
}
if shortcut.CreatorId != userID && currentUser.Role != store.RoleAdmin {
return nil, status.Errorf(codes.PermissionDenied, "Permission denied")
}
err = s.Store.DeleteShortcut(ctx, &store.DeleteShortcut{
ID: shortcut.Id,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete shortcut, err: %v", err)
}
response := &apiv2pb.DeleteShortcutResponse{}
return response, nil
}
func (s *ShortcutService) createShortcutCreateActivity(ctx context.Context, shortcut *storepb.Shortcut) error {
payload := &storepb.ActivityShorcutCreatePayload{
ShortcutId: shortcut.Id,
}
payloadStr, err := protojson.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 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,
},
}
}

View File

@ -3,31 +3,56 @@ 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/crypto/bcrypt"
"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(store *store.Store) *UserService {
func NewUserService(secret string, store *store.Store) *UserService {
return &UserService{
Secret: secret,
Store: store,
}
}
func (s *UserService) ListUsers(ctx context.Context, _ *apiv2pb.ListUsersRequest) (*apiv2pb.ListUsersResponse, error) {
users, err := s.Store.ListUsers(ctx, &store.FindUser{})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list users: %v", err)
}
userMessages := []*apiv2pb.User{}
for _, user := range users {
userMessages = append(userMessages, convertUserFromStore(user))
}
response := &apiv2pb.ListUsersResponse{
Users: userMessages,
}
return response, nil
}
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 list tags: %v", err)
return nil, status.Errorf(codes.Internal, "failed to find user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
@ -40,6 +65,206 @@ func (s *UserService) GetUser(ctx context.Context, request *apiv2pb.GetUserReque
return response, nil
}
func (s *UserService) CreateUser(ctx context.Context, request *apiv2pb.CreateUserRequest) (*apiv2pb.CreateUserResponse, error) {
passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to hash password: %v", err)
}
user, err := s.Store.CreateUser(ctx, &store.User{
Email: request.User.Email,
Nickname: request.User.Nickname,
Role: store.RoleUser,
PasswordHash: string(passwordHash),
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create user: %v", err)
}
response := &apiv2pb.CreateUserResponse{
User: convertUserFromStore(user),
}
return response, nil
}
func (s *UserService) DeleteUser(ctx context.Context, request *apiv2pb.DeleteUserRequest) (*apiv2pb.DeleteUserResponse, error) {
userID := ctx.Value(UserIDContextKey).(int32)
if userID == request.Id {
return nil, status.Errorf(codes.InvalidArgument, "cannot delete yourself")
}
err := s.Store.DeleteUser(ctx, &store.DeleteUser{
ID: request.Id,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete user: %v", err)
}
response := &apiv2pb.DeleteUserResponse{}
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),

View File

@ -29,7 +29,8 @@ func NewAPIV2Service(secret string, profile *profile.Profile, store *store.Store
authProvider.AuthenticationInterceptor,
),
)
apiv2pb.RegisterUserServiceServer(grpcServer, NewUserService(store))
apiv2pb.RegisterUserServiceServer(grpcServer, NewUserService(secret, store))
apiv2pb.RegisterShortcutServiceServer(grpcServer, NewShortcutService(secret, store))
return &APIV2Service{
Secret: secret,
@ -61,6 +62,9 @@ func (s *APIV2Service) RegisterGateway(ctx context.Context, e *echo.Echo) error
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

@ -8,14 +8,15 @@ import (
"os/signal"
"syscall"
"github.com/spf13/cobra"
"github.com/spf13/viper"
_ "modernc.org/sqlite"
"github.com/boojack/slash/internal/log"
"github.com/boojack/slash/server"
_profile "github.com/boojack/slash/server/profile"
"github.com/boojack/slash/store"
"github.com/boojack/slash/store/db"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
_ "modernc.org/sqlite"
)
const (
@ -36,7 +37,7 @@ var (
db := db.NewDB(profile)
if err := db.Open(ctx); err != nil {
cancel()
fmt.Printf("failed to open db, error: %+v\n", err)
log.Error("failed to open database", zap.Error(err))
return
}
@ -44,7 +45,7 @@ var (
s, err := server.NewServer(ctx, profile, storeInstance)
if err != nil {
cancel()
fmt.Printf("failed to create server, error: %+v\n", err)
log.Error("failed to create server", zap.Error(err))
return
}
@ -55,16 +56,16 @@ var (
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
sig := <-c
fmt.Printf("%s received.\n", sig.String())
log.Info(fmt.Sprintf("%s received.\n", sig.String()))
s.Shutdown(ctx)
cancel()
}()
println(greetingBanner)
fmt.Printf("Version %s has started at :%d\n", profile.Version, profile.Port)
printGreetings()
if err := s.Start(ctx); err != nil {
if err != http.ErrServerClosed {
fmt.Printf("failed to start server, error: %+v\n", err)
log.Error("failed to start server", zap.Error(err))
cancel()
}
}
@ -76,6 +77,7 @@ var (
)
func Execute() error {
defer log.Sync()
return rootCmd.Execute()
}
@ -109,7 +111,7 @@ func initConfig() {
var err error
profile, err = _profile.GetProfile()
if err != nil {
fmt.Printf("failed to get profile, error: %+v\n", err)
log.Error("failed to get profile", zap.Error(err))
return
}
@ -122,6 +124,15 @@ func initConfig() {
println("---")
}
func printGreetings() {
fmt.Println(greetingBanner)
fmt.Printf("Version %s has been started on port %d\n", profile.Version, profile.Port)
fmt.Println("---")
fmt.Println("See more in:")
fmt.Printf("👉GitHub: %s\n", "https://github.com/boojack/slash")
fmt.Println("---")
}
func main() {
err := Execute()
if err != nil {

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,45 @@
# 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
### Install the extension
For Chromuim based browsers, you can install the extension from the [Chrome Web Store](https://chrome.google.com/webstore/detail/slash/ebaiehmkammnacjadffpicipfckgeobg).
For Firefox, we don't support the Firefox Add-ons platform yet. And we are working on it.
### 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)
### 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)

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

57
extension/package.json Normal file
View File

@ -0,0 +1,57 @@
{
"name": "slash-extension",
"displayName": "Slash",
"version": "0.1.4",
"description": "An open source, self-hosted bookmarks and link sharing platform. Save and share your links very easily.",
"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": {
"@bufbuild/protobuf": "^1.3.0",
"@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": {
"omnibox": {
"keyword": "s"
},
"permissions": [
"tabs",
"storage"
]
}
}

7114
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: {},
},
};

View File

@ -0,0 +1,62 @@
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 });
}
});
chrome.omnibox.onInputEntered.addListener(async (text) => {
const shortcuts = (await storage.getItem<Shortcut[]>("shortcuts")) || [];
const shortcut = shortcuts.find((shortcut) => shortcut.name === text);
if (!shortcut) {
return;
}
return chrome.tabs.update({ 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,173 @@
import { Button, IconButton, Input, Modal, ModalDialog } 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 { CreateShortcutResponse, OpenGraphMetadata, Visibility } from "@/types/proto/api/v2/shortcut_service_pb";
import Icon from "./Icon";
const generateTempName = (length = 6) => {
let result = "";
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
};
interface State {
name: string;
title: string;
link: string;
}
const CreateShortcutsButton = () => {
const [domain] = useStorage("domain");
const [accessToken] = useStorage("access_token");
const [shortcuts, setShortcuts] = useStorage("shortcuts");
const [state, setState] = useState<State>({
name: "",
title: "",
link: "",
});
const [isLoading, setIsLoading] = useState(false);
const [showModal, setShowModal] = useState(false);
useEffect(() => {
if (showModal) {
document.body.style.height = "384px";
} else {
document.body.style.height = "auto";
}
}, [showModal]);
const handleCreateShortcutButtonClick = async () => {
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
if (tabs.length === 0) {
toast.error("No active tab found");
return;
}
const tab = tabs[0];
setState((state) => ({
...state,
name: generateTempName().toLowerCase() + "-temp",
title: tab.title || "",
link: tab.url || "",
}));
setShowModal(true);
});
};
const handleNameInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setState((state) => ({
...state,
name: e.target.value,
}));
};
const handleTitleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setState((state) => ({
...state,
title: e.target.value,
}));
};
const handleLinkInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setState((state) => ({
...state,
link: e.target.value,
}));
};
const handleSaveBtnClick = async () => {
if (isLoading) {
return;
}
if (!state.name) {
toast.error("Name is required");
return;
}
setIsLoading(true);
try {
const {
data: { shortcut },
} = await axios.post<CreateShortcutResponse>(
`${domain}/api/v2/shortcuts`,
{
name: state.name,
title: state.title,
link: state.link,
visibility: Visibility.PRIVATE,
ogMetadata: OpenGraphMetadata.fromJsonString("{}"),
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);
setShortcuts([shortcut, ...shortcuts]);
toast.success("Shortcut created successfully");
setShowModal(false);
} catch (error: any) {
console.error(error);
toast.error(error.response.data.message);
}
setIsLoading(false);
};
return (
<>
<IconButton color="primary" variant="solid" size="sm" onClick={() => handleCreateShortcutButtonClick()}>
<Icon.Plus className="w-5 h-auto" />
</IconButton>
<Modal container={() => document.body} open={showModal} onClose={() => setShowModal(false)}>
<ModalDialog className="w-3/4">
<div className="w-full flex flex-row justify-between items-center mb-2">
<span className="text-base font-medium">Create Shortcut</span>
<Button size="sm" variant="plain" onClick={() => setShowModal(false)}>
<Icon.X className="w-5 h-auto text-gray-600" />
</Button>
</div>
<div className="overflow-x-hidden w-full flex flex-col justify-start items-center">
<div className="w-full flex flex-row justify-start items-center mb-2">
<span className="block w-12 mr-2 shrink-0">Name</span>
<Input className="grow" type="text" placeholder="Unique shortcut name" value={state.name} onChange={handleNameInputChange} />
</div>
<div className="w-full flex flex-row justify-start items-center mb-2">
<span className="block w-12 mr-2 shrink-0">Title</span>
<Input className="grow" type="text" placeholder="Shortcut title" value={state.title} onChange={handleTitleInputChange} />
</div>
<div className="w-full flex flex-row justify-start items-center mb-2">
<span className="block w-12 mr-2 shrink-0">Link</span>
<Input
className="grow"
type="text"
placeholder="https://github.com/boojack/slash"
value={state.link}
onChange={handleLinkInputChange}
/>
</div>
<div className="w-full flex flex-row justify-end items-center mt-2 space-x-2">
<Button color="neutral" variant="plain" onClick={() => setShowModal(false)}>
Cancel
</Button>
<Button color="primary" disabled={isLoading} loading={isLoading} onClick={handleSaveBtnClick}>
Save
</Button>
</div>
</div>
</ModalDialog>
</Modal>
</>
);
};
export default CreateShortcutsButton;

View File

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

View File

@ -0,0 +1,12 @@
import classNames from "classnames";
import LogoBase64 from "data-base64:../..//assets/icon.png";
interface Props {
className?: string;
}
const Logo = ({ className }: Props) => {
return <img className={classNames(className)} src={LogoBase64} alt="" />;
};
export default Logo;

View File

@ -0,0 +1,45 @@
import { IconButton } from "@mui/joy";
import { useStorage } from "@plasmohq/storage/hook";
import axios from "axios";
import { useEffect } from "react";
import { toast } from "react-hot-toast";
import { ListShortcutsResponse } from "@/types/proto/api/v2/shortcut_service_pb";
import Icon from "./Icon";
const PullShortcutsButton = () => {
const [domain] = useStorage("domain");
const [accessToken] = useStorage("access_token");
const [, setShortcuts] = useStorage("shortcuts");
useEffect(() => {
if (domain && accessToken) {
handlePullShortcuts(true);
}
}, [domain, accessToken]);
const handlePullShortcuts = async (silence = false) => {
try {
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);
}
};
return (
<IconButton color="neutral" variant="plain" size="sm" onClick={() => handlePullShortcuts()}>
<Icon.RefreshCcw className="w-4 h-auto" />
</IconButton>
);
};
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);
};

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

@ -0,0 +1,134 @@
import type { Shortcut } from "./types/proto/api/v2/shortcut_service_pb";
import { Button, Divider, 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 Logo from "./components/Logo";
import PullShortcutsButton from "./components/PullShortcutsButton";
import ShortcutsContainer from "./components/ShortcutsContainer";
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,
});
const [shortcuts] = useStorage<Shortcut[]>("shortcuts", []);
const isInitialized = 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 flex flex-row justify-center items-center">
<a
className="bg-yellow-100 mt-12 py-2 px-3 rounded-full border flex flex-row justify-start items-center cursor-pointer shadow hover:underline hover:text-blue-600"
href="https://github.com/boojack/slash#browser-extension"
target="_blank"
>
<Icon.HelpCircle className="w-4 h-auto" />
<span className="mx-1 text-sm">Need help? Check out the docs</span>
<Icon.ExternalLink className="w-4 h-auto" />
</a>
</div>
<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 text-2xl">
<Logo className="w-10 h-auto mr-2" />
<span>Slash</span>
<span className="mx-2 text-gray-400">/</span>
<span>Setting</span>
</h2>
<div className="w-full flex flex-col justify-start items-start">
<div className="w-full flex flex-col justify-start items-start mb-4">
<div className="mb-2 text-base w-full flex flex-row justify-between items-center">
<span>Domain</span>
{domain !== "" && (
<a
className="text-sm flex flex-row justify-start items-center hover:underline hover:text-blue-600"
href={domain}
target="_blank"
>
<span className="mr-1">Go to my Slash</span>
<Icon.ExternalLink className="w-4 h-auto" />
</a>
)}
</div>
<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>
{isInitialized && (
<>
<Divider className="!my-6" />
<h2 className="flex flex-row justify-start items-center mb-4">
<span className="text-lg">Shortcuts</span>
<span className="text-gray-500 mr-1">({shortcuts.length})</span>
<PullShortcutsButton />
</h2>
<ShortcutsContainer />
</>
)}
</div>
</div>
<Toaster position="top-center" />
</>
);
};
export default IndexOptions;

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

@ -0,0 +1,109 @@
import { Button, Divider, IconButton } from "@mui/joy";
import { useStorage } from "@plasmohq/storage/hook";
import { Toaster } from "react-hot-toast";
import CreateShortcutsButton from "@/components/CreateShortcutsButton";
import Icon from "@/components/Icon";
import Logo from "@/components/Logo";
import PullShortcutsButton from "@/components/PullShortcutsButton";
import ShortcutsContainer from "@/components/ShortcutsContainer";
import { Shortcut } from "@/types/proto/api/v2/shortcut_service_pb";
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();
chrome.browserAction.setPopup({ popup: "" });
};
return (
<>
<div className="w-full min-w-[512px] px-4 pt-4">
<div className="w-full flex flex-row justify-between items-center">
<div className="flex flex-row justify-start items-center">
<Logo className="w-6 h-auto mr-2" />
<span className="">Slash</span>
{isInitialized && (
<>
<span className="mx-1 text-gray-400">/</span>
<span>Shortcuts</span>
<span className="text-gray-500 mr-0.5">({shortcuts.length})</span>
<PullShortcutsButton />
</>
)}
</div>
<div>{isInitialized && <CreateShortcutsButton />}</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>
)}
<Divider className="!mt-4 !mb-2 opacity-40" />
<div className="w-full flex flex-row justify-between items-center mb-2">
<div className="flex flex-row justify-start items-center">
<IconButton size="sm" variant="plain" color="neutral" onClick={handleSettingButtonClick}>
<Icon.Settings className="w-5 h-auto text-gray-500" />
</IconButton>
<IconButton
size="sm"
variant="plain"
color="neutral"
component="a"
href="https://github.com/boojack/slash"
target="_blank"
>
<Icon.Github className="w-5 h-auto text-gray-500" />
</IconButton>
</div>
<div className="flex flex-row justify-end items-center">
<a
className="text-sm flex flex-row justify-start items-center text-gray-500 hover:underline hover:text-blue-600"
href={domain}
target="_blank"
>
<span className="mr-1">Go to my Slash</span>
<Icon.ExternalLink className="w-4 h-auto" />
</a>
</div>
</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,329 @@
// @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;
}
/**
* @generated from message slash.api.v2.CreateShortcutRequest
*/
export declare class CreateShortcutRequest extends Message<CreateShortcutRequest> {
/**
* @generated from field: slash.api.v2.Shortcut shortcut = 1;
*/
shortcut?: Shortcut;
constructor(data?: PartialMessage<CreateShortcutRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.CreateShortcutRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateShortcutRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CreateShortcutRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CreateShortcutRequest;
static equals(a: CreateShortcutRequest | PlainMessage<CreateShortcutRequest> | undefined, b: CreateShortcutRequest | PlainMessage<CreateShortcutRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.CreateShortcutResponse
*/
export declare class CreateShortcutResponse extends Message<CreateShortcutResponse> {
/**
* @generated from field: slash.api.v2.Shortcut shortcut = 1;
*/
shortcut?: Shortcut;
constructor(data?: PartialMessage<CreateShortcutResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.CreateShortcutResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateShortcutResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CreateShortcutResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CreateShortcutResponse;
static equals(a: CreateShortcutResponse | PlainMessage<CreateShortcutResponse> | undefined, b: CreateShortcutResponse | PlainMessage<CreateShortcutResponse> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.DeleteShortcutRequest
*/
export declare class DeleteShortcutRequest extends Message<DeleteShortcutRequest> {
/**
* @generated from field: string name = 1;
*/
name: string;
constructor(data?: PartialMessage<DeleteShortcutRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.DeleteShortcutRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DeleteShortcutRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DeleteShortcutRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DeleteShortcutRequest;
static equals(a: DeleteShortcutRequest | PlainMessage<DeleteShortcutRequest> | undefined, b: DeleteShortcutRequest | PlainMessage<DeleteShortcutRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.DeleteShortcutResponse
*/
export declare class DeleteShortcutResponse extends Message<DeleteShortcutResponse> {
constructor(data?: PartialMessage<DeleteShortcutResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.DeleteShortcutResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DeleteShortcutResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DeleteShortcutResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DeleteShortcutResponse;
static equals(a: DeleteShortcutResponse | PlainMessage<DeleteShortcutResponse> | undefined, b: DeleteShortcutResponse | PlainMessage<DeleteShortcutResponse> | undefined): boolean;
}

View File

@ -0,0 +1,130 @@
// @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 },
],
);
/**
* @generated from message slash.api.v2.CreateShortcutRequest
*/
export const CreateShortcutRequest = proto3.makeMessageType(
"slash.api.v2.CreateShortcutRequest",
() => [
{ no: 1, name: "shortcut", kind: "message", T: Shortcut },
],
);
/**
* @generated from message slash.api.v2.CreateShortcutResponse
*/
export const CreateShortcutResponse = proto3.makeMessageType(
"slash.api.v2.CreateShortcutResponse",
() => [
{ no: 1, name: "shortcut", kind: "message", T: Shortcut },
],
);
/**
* @generated from message slash.api.v2.DeleteShortcutRequest
*/
export const DeleteShortcutRequest = proto3.makeMessageType(
"slash.api.v2.DeleteShortcutRequest",
() => [
{ no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
],
);
/**
* @generated from message slash.api.v2.DeleteShortcutResponse
*/
export const DeleteShortcutResponse = proto3.makeMessageType(
"slash.api.v2.DeleteShortcutResponse",
[],
);

View File

@ -0,0 +1,466 @@
// @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;
/**
* @generated from field: string password = 9;
*/
password: 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.ListUsersRequest
*/
export declare class ListUsersRequest extends Message<ListUsersRequest> {
constructor(data?: PartialMessage<ListUsersRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.ListUsersRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ListUsersRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ListUsersRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ListUsersRequest;
static equals(a: ListUsersRequest | PlainMessage<ListUsersRequest> | undefined, b: ListUsersRequest | PlainMessage<ListUsersRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.ListUsersResponse
*/
export declare class ListUsersResponse extends Message<ListUsersResponse> {
/**
* @generated from field: repeated slash.api.v2.User users = 1;
*/
users: User[];
constructor(data?: PartialMessage<ListUsersResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.ListUsersResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ListUsersResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ListUsersResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ListUsersResponse;
static equals(a: ListUsersResponse | PlainMessage<ListUsersResponse> | undefined, b: ListUsersResponse | PlainMessage<ListUsersResponse> | 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.CreateUserRequest
*/
export declare class CreateUserRequest extends Message<CreateUserRequest> {
/**
* @generated from field: slash.api.v2.User user = 1;
*/
user?: User;
constructor(data?: PartialMessage<CreateUserRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.CreateUserRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateUserRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CreateUserRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CreateUserRequest;
static equals(a: CreateUserRequest | PlainMessage<CreateUserRequest> | undefined, b: CreateUserRequest | PlainMessage<CreateUserRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.CreateUserResponse
*/
export declare class CreateUserResponse extends Message<CreateUserResponse> {
/**
* @generated from field: slash.api.v2.User user = 1;
*/
user?: User;
constructor(data?: PartialMessage<CreateUserResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.CreateUserResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateUserResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CreateUserResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CreateUserResponse;
static equals(a: CreateUserResponse | PlainMessage<CreateUserResponse> | undefined, b: CreateUserResponse | PlainMessage<CreateUserResponse> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.DeleteUserRequest
*/
export declare class DeleteUserRequest extends Message<DeleteUserRequest> {
/**
* @generated from field: int32 id = 1;
*/
id: number;
constructor(data?: PartialMessage<DeleteUserRequest>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.DeleteUserRequest";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DeleteUserRequest;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DeleteUserRequest;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DeleteUserRequest;
static equals(a: DeleteUserRequest | PlainMessage<DeleteUserRequest> | undefined, b: DeleteUserRequest | PlainMessage<DeleteUserRequest> | undefined): boolean;
}
/**
* @generated from message slash.api.v2.DeleteUserResponse
*/
export declare class DeleteUserResponse extends Message<DeleteUserResponse> {
constructor(data?: PartialMessage<DeleteUserResponse>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.api.v2.DeleteUserResponse";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): DeleteUserResponse;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): DeleteUserResponse;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): DeleteUserResponse;
static equals(a: DeleteUserResponse | PlainMessage<DeleteUserResponse> | undefined, b: DeleteUserResponse | PlainMessage<DeleteUserResponse> | 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,186 @@
// @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 */ },
{ no: 9, name: "password", kind: "scalar", T: 9 /* ScalarType.STRING */ },
],
);
/**
* @generated from message slash.api.v2.ListUsersRequest
*/
export const ListUsersRequest = proto3.makeMessageType(
"slash.api.v2.ListUsersRequest",
[],
);
/**
* @generated from message slash.api.v2.ListUsersResponse
*/
export const ListUsersResponse = proto3.makeMessageType(
"slash.api.v2.ListUsersResponse",
() => [
{ no: 1, name: "users", kind: "message", T: User, repeated: true },
],
);
/**
* @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.CreateUserRequest
*/
export const CreateUserRequest = proto3.makeMessageType(
"slash.api.v2.CreateUserRequest",
() => [
{ no: 1, name: "user", kind: "message", T: User },
],
);
/**
* @generated from message slash.api.v2.CreateUserResponse
*/
export const CreateUserResponse = proto3.makeMessageType(
"slash.api.v2.CreateUserResponse",
() => [
{ no: 1, name: "user", kind: "message", T: User },
],
);
/**
* @generated from message slash.api.v2.DeleteUserRequest
*/
export const DeleteUserRequest = proto3.makeMessageType(
"slash.api.v2.DeleteUserRequest",
() => [
{ no: 1, name: "id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
],
);
/**
* @generated from message slash.api.v2.DeleteUserResponse
*/
export const DeleteUserResponse = proto3.makeMessageType(
"slash.api.v2.DeleteUserResponse",
[],
);
/**
* @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,32 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file store/activity.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 message slash.store.ActivityShorcutCreatePayload
*/
export declare class ActivityShorcutCreatePayload extends Message<ActivityShorcutCreatePayload> {
/**
* @generated from field: int32 shortcut_id = 1;
*/
shortcutId: number;
constructor(data?: PartialMessage<ActivityShorcutCreatePayload>);
static readonly runtime: typeof proto3;
static readonly typeName = "slash.store.ActivityShorcutCreatePayload";
static readonly fields: FieldList;
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ActivityShorcutCreatePayload;
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ActivityShorcutCreatePayload;
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ActivityShorcutCreatePayload;
static equals(a: ActivityShorcutCreatePayload | PlainMessage<ActivityShorcutCreatePayload> | undefined, b: ActivityShorcutCreatePayload | PlainMessage<ActivityShorcutCreatePayload> | undefined): boolean;
}

View File

@ -0,0 +1,17 @@
// @generated by protoc-gen-es v1.3.0
// @generated from file store/activity.proto (package slash.store, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { proto3 } from "@bufbuild/protobuf";
/**
* @generated from message slash.store.ActivityShorcutCreatePayload
*/
export const ActivityShorcutCreatePayload = proto3.makeMessageType(
"slash.store.ActivityShorcutCreatePayload",
() => [
{ no: 1, name: "shortcut_id", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
],
);

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": "."
}
}

4
go.mod
View File

@ -50,6 +50,8 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // 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
@ -70,9 +72,11 @@ require (
require (
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2
github.com/h2non/filetype v1.1.3
github.com/mssola/useragent v1.0.0
github.com/pkg/errors v0.9.1
go.deanishe.net/favicon v0.1.0
go.uber.org/zap v1.21.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

22
go.sum
View File

@ -44,6 +44,8 @@ github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5z
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@ -171,6 +173,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
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/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
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=
@ -290,6 +294,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.deanishe.net/favicon v0.1.0 h1:Afy941gjRik+DjUUcYHUxcztFEeFse2ITBkMMOlgefM=
go.deanishe.net/favicon v0.1.0/go.mod h1:vIKVI+lUh8k3UAzaN4gjC+cpyatLQWmx0hVX4vLE8jU=
go.mongodb.org/mongo-driver v1.4.2/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
@ -299,6 +304,16 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
@ -346,6 +361,7 @@ 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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=
@ -380,6 +396,7 @@ 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.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
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=
@ -402,6 +419,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -440,7 +458,9 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -515,6 +535,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -630,6 +651,7 @@ 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=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,72 +0,0 @@
package errorutil
import (
"errors"
)
// Code is the error code.
type Code int
// Application error codes.
const (
// 0 ~ 99 general error.
Ok Code = 0
Internal Code = 1
NotAuthorized Code = 2
Invalid Code = 3
NotFound Code = 4
Conflict Code = 5
NotImplemented Code = 6
)
// Error represents an application-specific error. Application errors can be
// unwrapped by the caller to extract out the code & message.
//
// Any non-application error (such as a disk error) should be reported as an
// Internal error and the human user should only see "Internal error" as the
// message. These low-level internal error details should only be logged and
// reported to the operator of the application (not the end user).
type Error struct {
// Machine-readable error code.
Code Code
// Embedded error.
Err error
}
// Error implements the error interface. Not used by the application otherwise.
func (e *Error) Error() string {
return e.Err.Error()
}
// ErrorCode unwraps an application error and returns its code.
// Non-application errors always return EINTERNAL.
func ErrorCode(err error) Code {
var e *Error
if err == nil {
return Ok
} else if errors.As(err, &e) {
return e.Code
}
return Internal
}
// ErrorMessage unwraps an application error and returns its message.
// Non-application errors always return "Internal error".
func ErrorMessage(err error) string {
var e *Error
if err == nil {
return ""
} else if errors.As(err, &e) {
return e.Err.Error()
}
return "Internal error."
}
// Errorf is a helper function to return an Error with a given code and error.
func Errorf(code Code, err error) *Error {
return &Error{
Code: code,
Err: err,
}
}

66
internal/log/logger.go Normal file
View File

@ -0,0 +1,66 @@
package log
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
// `gl` is the global logger.
// Other packages should use public methods such as Info/Error to do the logging.
// For other types of logging, e.g. logging to a separate file, they should use their own loggers.
gl *zap.Logger
gLevel zap.AtomicLevel
)
// Initializes the global console logger.
func init() {
gLevel = zap.NewAtomicLevelAt(zap.InfoLevel)
gl, _ = zap.Config{
Level: gLevel,
Development: true,
// Use "console" to print readable stacktrace.
Encoding: "console",
EncoderConfig: zap.NewDevelopmentEncoderConfig(),
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}.Build(
// Skip one caller stack to locate the correct caller.
zap.AddCallerSkip(1),
)
}
// SetLevel wraps the zap Level's SetLevel method.
func SetLevel(level zapcore.Level) {
gLevel.SetLevel(level)
}
// EnabledLevel wraps the zap Level's Enabled method.
func EnabledLevel(level zapcore.Level) bool {
return gLevel.Enabled(level)
}
// Debug wraps the zap Logger's Debug method.
func Debug(msg string, fields ...zap.Field) {
gl.Debug(msg, fields...)
}
// Info wraps the zap Logger's Info method.
func Info(msg string, fields ...zap.Field) {
gl.Info(msg, fields...)
}
// Warn wraps the zap Logger's Warn method.
func Warn(msg string, fields ...zap.Field) {
gl.Warn(msg, fields...)
}
// Error wraps the zap Logger's Error method.
func Error(msg string, fields ...zap.Field) {
gl.Error(msg, fields...)
}
// Sync wraps the zap Logger's Sync method.
func Sync() {
_ = gl.Sync()
}

View File

@ -0,0 +1,105 @@
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 {
// ListShortcuts returns a list of shortcuts.
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";
}
// CreateShortcut creates a shortcut.
rpc CreateShortcut(CreateShortcutRequest) returns (CreateShortcutResponse) {
option (google.api.http) = {
post: "/api/v2/shortcuts"
body: "shortcut"
};
}
// DeleteShortcut deletes a shortcut by name.
rpc DeleteShortcut(DeleteShortcutRequest) returns (DeleteShortcutResponse) {
option (google.api.http) = {delete: "/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;
}
message CreateShortcutRequest {
Shortcut shortcut = 1;
}
message CreateShortcutResponse {
Shortcut shortcut = 1;
}
message DeleteShortcutRequest {
string name = 1;
}
message DeleteShortcutResponse {}

View File

@ -5,14 +5,50 @@ 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 {
// ListUsers returns a list of users.
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
option (google.api.http) = {get: "/api/v2/users"};
}
// 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";
}
// CreateUser creates a new user.
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
option (google.api.http) = {
post: "/api/v2/users"
body: "user"
};
}
// DeleteUser deletes a user by id.
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse) {
option (google.api.http) = {delete: "/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 {
@ -29,6 +65,8 @@ message User {
string email = 7;
string nickname = 8;
string password = 9;
}
enum Role {
@ -39,6 +77,12 @@ enum Role {
USER = 2;
}
message ListUsersRequest {}
message ListUsersResponse {
repeated User users = 1;
}
message GetUserRequest {
int32 id = 1;
}
@ -46,3 +90,53 @@ message GetUserRequest {
message GetUserResponse {
User user = 1;
}
message CreateUserRequest {
User user = 1;
}
message CreateUserResponse {
User user = 1;
}
message DeleteUserRequest {
int32 id = 1;
}
message DeleteUserResponse {}
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;
}

View File

@ -18,6 +18,11 @@ plugins:
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:

View File

@ -6,10 +6,39 @@
- [api/v2/common.proto](#api_v2_common-proto)
- [RowStatus](#slash-api-v2-RowStatus)
- [api/v2/shortcut_service.proto](#api_v2_shortcut_service-proto)
- [CreateShortcutRequest](#slash-api-v2-CreateShortcutRequest)
- [CreateShortcutResponse](#slash-api-v2-CreateShortcutResponse)
- [DeleteShortcutRequest](#slash-api-v2-DeleteShortcutRequest)
- [DeleteShortcutResponse](#slash-api-v2-DeleteShortcutResponse)
- [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)
- [CreateUserRequest](#slash-api-v2-CreateUserRequest)
- [CreateUserResponse](#slash-api-v2-CreateUserResponse)
- [DeleteUserAccessTokenRequest](#slash-api-v2-DeleteUserAccessTokenRequest)
- [DeleteUserAccessTokenResponse](#slash-api-v2-DeleteUserAccessTokenResponse)
- [DeleteUserRequest](#slash-api-v2-DeleteUserRequest)
- [DeleteUserResponse](#slash-api-v2-DeleteUserResponse)
- [GetUserRequest](#slash-api-v2-GetUserRequest)
- [GetUserResponse](#slash-api-v2-GetUserResponse)
- [ListUserAccessTokensRequest](#slash-api-v2-ListUserAccessTokensRequest)
- [ListUserAccessTokensResponse](#slash-api-v2-ListUserAccessTokensResponse)
- [ListUsersRequest](#slash-api-v2-ListUsersRequest)
- [ListUsersResponse](#slash-api-v2-ListUsersResponse)
- [User](#slash-api-v2-User)
- [UserAccessToken](#slash-api-v2-UserAccessToken)
- [Role](#slash-api-v2-Role)
@ -48,6 +77,202 @@
<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-CreateShortcutRequest"></a>
### CreateShortcutRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| shortcut | [Shortcut](#slash-api-v2-Shortcut) | | |
<a name="slash-api-v2-CreateShortcutResponse"></a>
### CreateShortcutResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| shortcut | [Shortcut](#slash-api-v2-Shortcut) | | |
<a name="slash-api-v2-DeleteShortcutRequest"></a>
### DeleteShortcutRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| name | [string](#string) | | |
<a name="slash-api-v2-DeleteShortcutResponse"></a>
### DeleteShortcutResponse
<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) | ListShortcuts returns a list of shortcuts. |
| GetShortcut | [GetShortcutRequest](#slash-api-v2-GetShortcutRequest) | [GetShortcutResponse](#slash-api-v2-GetShortcutResponse) | GetShortcut returns a shortcut by name. |
| CreateShortcut | [CreateShortcutRequest](#slash-api-v2-CreateShortcutRequest) | [CreateShortcutResponse](#slash-api-v2-CreateShortcutResponse) | CreateShortcut creates a shortcut. |
| DeleteShortcut | [DeleteShortcutRequest](#slash-api-v2-DeleteShortcutRequest) | [DeleteShortcutResponse](#slash-api-v2-DeleteShortcutResponse) | DeleteShortcut deletes a shortcut by name. |
<a name="api_v2_user_service-proto"></a>
<p align="right"><a href="#top">Top</a></p>
@ -55,6 +280,118 @@
<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-CreateUserRequest"></a>
### CreateUserRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| user | [User](#slash-api-v2-User) | | |
<a name="slash-api-v2-CreateUserResponse"></a>
### CreateUserResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| user | [User](#slash-api-v2-User) | | |
<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-DeleteUserRequest"></a>
### DeleteUserRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | |
<a name="slash-api-v2-DeleteUserResponse"></a>
### DeleteUserResponse
<a name="slash-api-v2-GetUserRequest"></a>
### GetUserRequest
@ -85,6 +422,61 @@
<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-ListUsersRequest"></a>
### ListUsersRequest
<a name="slash-api-v2-ListUsersResponse"></a>
### ListUsersResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| users | [User](#slash-api-v2-User) | repeated | |
<a name="slash-api-v2-User"></a>
### User
@ -100,6 +492,25 @@
| role | [Role](#slash-api-v2-Role) | | |
| email | [string](#string) | | |
| nickname | [string](#string) | | |
| password | [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) | | |
@ -132,7 +543,13 @@
| Method Name | Request Type | Response Type | Description |
| ----------- | ------------ | ------------- | ------------|
| GetUser | [GetUserRequest](#slash-api-v2-GetUserRequest) | [GetUserResponse](#slash-api-v2-GetUserResponse) | |
| ListUsers | [ListUsersRequest](#slash-api-v2-ListUsersRequest) | [ListUsersResponse](#slash-api-v2-ListUsersResponse) | ListUsers returns a list of users. |
| GetUser | [GetUserRequest](#slash-api-v2-GetUserRequest) | [GetUserResponse](#slash-api-v2-GetUserResponse) | GetUser returns a user by id. |
| CreateUser | [CreateUserRequest](#slash-api-v2-CreateUserRequest) | [CreateUserResponse](#slash-api-v2-CreateUserResponse) | CreateUser creates a new user. |
| DeleteUser | [DeleteUserRequest](#slash-api-v2-DeleteUserRequest) | [DeleteUserResponse](#slash-api-v2-DeleteUserResponse) | DeleteUser deletes 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. |

View File

@ -0,0 +1,953 @@
// 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
}
type CreateShortcutRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Shortcut *Shortcut `protobuf:"bytes,1,opt,name=shortcut,proto3" json:"shortcut,omitempty"`
}
func (x *CreateShortcutRequest) Reset() {
*x = CreateShortcutRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CreateShortcutRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CreateShortcutRequest) ProtoMessage() {}
func (x *CreateShortcutRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_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 CreateShortcutRequest.ProtoReflect.Descriptor instead.
func (*CreateShortcutRequest) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{6}
}
func (x *CreateShortcutRequest) GetShortcut() *Shortcut {
if x != nil {
return x.Shortcut
}
return nil
}
type CreateShortcutResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Shortcut *Shortcut `protobuf:"bytes,1,opt,name=shortcut,proto3" json:"shortcut,omitempty"`
}
func (x *CreateShortcutResponse) Reset() {
*x = CreateShortcutResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CreateShortcutResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CreateShortcutResponse) ProtoMessage() {}
func (x *CreateShortcutResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_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 CreateShortcutResponse.ProtoReflect.Descriptor instead.
func (*CreateShortcutResponse) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{7}
}
func (x *CreateShortcutResponse) GetShortcut() *Shortcut {
if x != nil {
return x.Shortcut
}
return nil
}
type DeleteShortcutRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *DeleteShortcutRequest) Reset() {
*x = DeleteShortcutRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteShortcutRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteShortcutRequest) ProtoMessage() {}
func (x *DeleteShortcutRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_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 DeleteShortcutRequest.ProtoReflect.Descriptor instead.
func (*DeleteShortcutRequest) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{8}
}
func (x *DeleteShortcutRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type DeleteShortcutResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *DeleteShortcutResponse) Reset() {
*x = DeleteShortcutResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_api_v2_shortcut_service_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DeleteShortcutResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteShortcutResponse) ProtoMessage() {}
func (x *DeleteShortcutResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v2_shortcut_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 DeleteShortcutResponse.ProtoReflect.Descriptor instead.
func (*DeleteShortcutResponse) Descriptor() ([]byte, []int) {
return file_api_v2_shortcut_service_proto_rawDescGZIP(), []int{9}
}
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, 0x22, 0x4b, 0x0a, 0x15, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 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, 0x22, 0x4c, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 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, 0x22, 0x2b, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 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, 0x18, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x68, 0x6f, 0x72,
0x74, 0x63, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 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, 0x32, 0x8d,
0x04, 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, 0x12, 0x80, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53,
0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x12, 0x23, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x68, 0x6f,
0x72, 0x74, 0x63, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x73,
0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x3a, 0x08, 0x73, 0x68, 0x6f, 0x72,
0x74, 0x63, 0x75, 0x74, 0x22, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x73, 0x68,
0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x73, 0x12, 0x84, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65,
0x74, 0x65, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x12, 0x23, 0x2e, 0x73, 0x6c, 0x61,
0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x24, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 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, 0x2a, 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, 10)
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
(*CreateShortcutRequest)(nil), // 7: slash.api.v2.CreateShortcutRequest
(*CreateShortcutResponse)(nil), // 8: slash.api.v2.CreateShortcutResponse
(*DeleteShortcutRequest)(nil), // 9: slash.api.v2.DeleteShortcutRequest
(*DeleteShortcutResponse)(nil), // 10: slash.api.v2.DeleteShortcutResponse
(RowStatus)(0), // 11: slash.api.v2.RowStatus
}
var file_api_v2_shortcut_service_proto_depIdxs = []int32{
11, // 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
1, // 5: slash.api.v2.CreateShortcutRequest.shortcut:type_name -> slash.api.v2.Shortcut
1, // 6: slash.api.v2.CreateShortcutResponse.shortcut:type_name -> slash.api.v2.Shortcut
3, // 7: slash.api.v2.ShortcutService.ListShortcuts:input_type -> slash.api.v2.ListShortcutsRequest
5, // 8: slash.api.v2.ShortcutService.GetShortcut:input_type -> slash.api.v2.GetShortcutRequest
7, // 9: slash.api.v2.ShortcutService.CreateShortcut:input_type -> slash.api.v2.CreateShortcutRequest
9, // 10: slash.api.v2.ShortcutService.DeleteShortcut:input_type -> slash.api.v2.DeleteShortcutRequest
4, // 11: slash.api.v2.ShortcutService.ListShortcuts:output_type -> slash.api.v2.ListShortcutsResponse
6, // 12: slash.api.v2.ShortcutService.GetShortcut:output_type -> slash.api.v2.GetShortcutResponse
8, // 13: slash.api.v2.ShortcutService.CreateShortcut:output_type -> slash.api.v2.CreateShortcutResponse
10, // 14: slash.api.v2.ShortcutService.DeleteShortcut:output_type -> slash.api.v2.DeleteShortcutResponse
11, // [11:15] is the sub-list for method output_type
7, // [7:11] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] 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
}
}
file_api_v2_shortcut_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CreateShortcutRequest); 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[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CreateShortcutResponse); 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[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteShortcutRequest); 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[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DeleteShortcutResponse); 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: 10,
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,446 @@
// 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
}
func request_ShortcutService_CreateShortcut_0(ctx context.Context, marshaler runtime.Marshaler, client ShortcutServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreateShortcutRequest
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.Shortcut); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CreateShortcut(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ShortcutService_CreateShortcut_0(ctx context.Context, marshaler runtime.Marshaler, server ShortcutServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreateShortcutRequest
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.Shortcut); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CreateShortcut(ctx, &protoReq)
return msg, metadata, err
}
func request_ShortcutService_DeleteShortcut_0(ctx context.Context, marshaler runtime.Marshaler, client ShortcutServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteShortcutRequest
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.DeleteShortcut(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ShortcutService_DeleteShortcut_0(ctx context.Context, marshaler runtime.Marshaler, server ShortcutServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteShortcutRequest
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.DeleteShortcut(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()...)
})
mux.Handle("POST", pattern_ShortcutService_CreateShortcut_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/CreateShortcut", runtime.WithHTTPPathPattern("/api/v2/shortcuts"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ShortcutService_CreateShortcut_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_CreateShortcut_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_ShortcutService_DeleteShortcut_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/DeleteShortcut", runtime.WithHTTPPathPattern("/api/v2/shortcuts/{name}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ShortcutService_DeleteShortcut_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_DeleteShortcut_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()...)
})
mux.Handle("POST", pattern_ShortcutService_CreateShortcut_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/CreateShortcut", runtime.WithHTTPPathPattern("/api/v2/shortcuts"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ShortcutService_CreateShortcut_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_CreateShortcut_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_ShortcutService_DeleteShortcut_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/DeleteShortcut", runtime.WithHTTPPathPattern("/api/v2/shortcuts/{name}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ShortcutService_DeleteShortcut_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_DeleteShortcut_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"}, ""))
pattern_ShortcutService_CreateShortcut_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v2", "shortcuts"}, ""))
pattern_ShortcutService_DeleteShortcut_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
forward_ShortcutService_CreateShortcut_0 = runtime.ForwardResponseMessage
forward_ShortcutService_DeleteShortcut_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/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"
ShortcutService_CreateShortcut_FullMethodName = "/slash.api.v2.ShortcutService/CreateShortcut"
ShortcutService_DeleteShortcut_FullMethodName = "/slash.api.v2.ShortcutService/DeleteShortcut"
)
// 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 returns a list of shortcuts.
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)
// CreateShortcut creates a shortcut.
CreateShortcut(ctx context.Context, in *CreateShortcutRequest, opts ...grpc.CallOption) (*CreateShortcutResponse, error)
// DeleteShortcut deletes a shortcut by name.
DeleteShortcut(ctx context.Context, in *DeleteShortcutRequest, opts ...grpc.CallOption) (*DeleteShortcutResponse, 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
}
func (c *shortcutServiceClient) CreateShortcut(ctx context.Context, in *CreateShortcutRequest, opts ...grpc.CallOption) (*CreateShortcutResponse, error) {
out := new(CreateShortcutResponse)
err := c.cc.Invoke(ctx, ShortcutService_CreateShortcut_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *shortcutServiceClient) DeleteShortcut(ctx context.Context, in *DeleteShortcutRequest, opts ...grpc.CallOption) (*DeleteShortcutResponse, error) {
out := new(DeleteShortcutResponse)
err := c.cc.Invoke(ctx, ShortcutService_DeleteShortcut_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 returns a list of shortcuts.
ListShortcuts(context.Context, *ListShortcutsRequest) (*ListShortcutsResponse, error)
// GetShortcut returns a shortcut by name.
GetShortcut(context.Context, *GetShortcutRequest) (*GetShortcutResponse, error)
// CreateShortcut creates a shortcut.
CreateShortcut(context.Context, *CreateShortcutRequest) (*CreateShortcutResponse, error)
// DeleteShortcut deletes a shortcut by name.
DeleteShortcut(context.Context, *DeleteShortcutRequest) (*DeleteShortcutResponse, 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) CreateShortcut(context.Context, *CreateShortcutRequest) (*CreateShortcutResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateShortcut not implemented")
}
func (UnimplementedShortcutServiceServer) DeleteShortcut(context.Context, *DeleteShortcutRequest) (*DeleteShortcutResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteShortcut 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)
}
func _ShortcutService_CreateShortcut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateShortcutRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ShortcutServiceServer).CreateShortcut(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ShortcutService_CreateShortcut_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ShortcutServiceServer).CreateShortcut(ctx, req.(*CreateShortcutRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ShortcutService_DeleteShortcut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteShortcutRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ShortcutServiceServer).DeleteShortcut(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ShortcutService_DeleteShortcut_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ShortcutServiceServer).DeleteShortcut(ctx, req.(*DeleteShortcutRequest))
}
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,
},
{
MethodName: "CreateShortcut",
Handler: _ShortcutService_CreateShortcut_Handler,
},
{
MethodName: "DeleteShortcut",
Handler: _ShortcutService_DeleteShortcut_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/v2/shortcut_service.proto",
}

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,24 @@ var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_UserService_ListUsers_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUsersRequest
var metadata runtime.ServerMetadata
msg, err := client.ListUsers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_ListUsers_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListUsersRequest
var metadata runtime.ServerMetadata
msg, err := server.ListUsers(ctx, &protoReq)
return msg, metadata, err
}
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
@ -83,12 +101,315 @@ func local_request_UserService_GetUser_0(ctx context.Context, marshaler runtime.
}
func request_UserService_CreateUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreateUserRequest
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.User); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CreateUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_CreateUser_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreateUserRequest
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.User); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CreateUser(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_DeleteUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteUserRequest
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.DeleteUser(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_DeleteUser_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq DeleteUserRequest
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.DeleteUser(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_ListUsers_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/ListUsers", runtime.WithHTTPPathPattern("/api/v2/users"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_ListUsers_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_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
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()
@ -114,6 +435,131 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("POST", pattern_UserService_CreateUser_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/CreateUser", runtime.WithHTTPPathPattern("/api/v2/users"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_CreateUser_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_CreateUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_UserService_DeleteUser_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/DeleteUser", runtime.WithHTTPPathPattern("/api/v2/users/{id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_DeleteUser_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_DeleteUser_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
}
@ -155,6 +601,28 @@ func RegisterUserServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn
// "UserServiceClient" to call the correct interceptors.
func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client UserServiceClient) error {
mux.Handle("GET", pattern_UserService_ListUsers_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/ListUsers", runtime.WithHTTPPathPattern("/api/v2/users"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_ListUsers_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_ListUsers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
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()
@ -177,13 +645,147 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("POST", pattern_UserService_CreateUser_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/CreateUser", runtime.WithHTTPPathPattern("/api/v2/users"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_CreateUser_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_CreateUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("DELETE", pattern_UserService_DeleteUser_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/DeleteUser", runtime.WithHTTPPathPattern("/api/v2/users/{id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_DeleteUser_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_DeleteUser_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_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v2", "users"}, ""))
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_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v2", "users"}, ""))
pattern_UserService_DeleteUser_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_ListUsers_0 = runtime.ForwardResponseMessage
forward_UserService_GetUser_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUser_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserAccessTokens_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUserAccessToken_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUserAccessToken_0 = runtime.ForwardResponseMessage
)

View File

@ -19,14 +19,33 @@ import (
const _ = grpc.SupportPackageIsVersion7
const (
UserService_ListUsers_FullMethodName = "/slash.api.v2.UserService/ListUsers"
UserService_GetUser_FullMethodName = "/slash.api.v2.UserService/GetUser"
UserService_CreateUser_FullMethodName = "/slash.api.v2.UserService/CreateUser"
UserService_DeleteUser_FullMethodName = "/slash.api.v2.UserService/DeleteUser"
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 {
// ListUsers returns a list of users.
ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error)
// GetUser returns a user by id.
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error)
// CreateUser creates a new user.
CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*CreateUserResponse, error)
// DeleteUser deletes a user by id.
DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*DeleteUserResponse, 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 {
@ -37,6 +56,15 @@ func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
return &userServiceClient{cc}
}
func (c *userServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) {
out := new(ListUsersResponse)
err := c.cc.Invoke(ctx, UserService_ListUsers_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
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...)
@ -46,11 +74,69 @@ func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opt
return out, nil
}
func (c *userServiceClient) CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*CreateUserResponse, error) {
out := new(CreateUserResponse)
err := c.cc.Invoke(ctx, UserService_CreateUser_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*DeleteUserResponse, error) {
out := new(DeleteUserResponse)
err := c.cc.Invoke(ctx, UserService_DeleteUser_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 {
// ListUsers returns a list of users.
ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error)
// GetUser returns a user by id.
GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)
// CreateUser creates a new user.
CreateUser(context.Context, *CreateUserRequest) (*CreateUserResponse, error)
// DeleteUser deletes a user by id.
DeleteUser(context.Context, *DeleteUserRequest) (*DeleteUserResponse, 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()
}
@ -58,9 +144,27 @@ type UserServiceServer interface {
type UnimplementedUserServiceServer struct {
}
func (UnimplementedUserServiceServer) ListUsers(context.Context, *ListUsersRequest) (*ListUsersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUsers not implemented")
}
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
}
func (UnimplementedUserServiceServer) CreateUser(context.Context, *CreateUserRequest) (*CreateUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateUser not implemented")
}
func (UnimplementedUserServiceServer) DeleteUser(context.Context, *DeleteUserRequest) (*DeleteUserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteUser 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.
@ -74,6 +178,24 @@ func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
s.RegisterService(&UserService_ServiceDesc, srv)
}
func _UserService_ListUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListUsersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).ListUsers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_ListUsers_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).ListUsers(ctx, req.(*ListUsersRequest))
}
return interceptor(ctx, in, info, handler)
}
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 {
@ -92,6 +214,96 @@ func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func
return interceptor(ctx, in, info, handler)
}
func _UserService_CreateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateUserRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).CreateUser(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_CreateUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).CreateUser(ctx, req.(*CreateUserRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_DeleteUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteUserRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).DeleteUser(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_DeleteUser_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).DeleteUser(ctx, req.(*DeleteUserRequest))
}
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)
@ -99,10 +311,34 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "slash.api.v2.UserService",
HandlerType: (*UserServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ListUsers",
Handler: _UserService_ListUsers_Handler,
},
{
MethodName: "GetUser",
Handler: _UserService_GetUser_Handler,
},
{
MethodName: "CreateUser",
Handler: _UserService_CreateUser_Handler,
},
{
MethodName: "DeleteUser",
Handler: _UserService_DeleteUser_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",

View File

@ -3,6 +3,9 @@
## Table of Contents
- [store/activity.proto](#store_activity-proto)
- [ActivityShorcutCreatePayload](#slash-store-ActivityShorcutCreatePayload)
- [store/common.proto](#store_common-proto)
- [RowStatus](#slash-store-RowStatus)
@ -12,10 +15,48 @@
- [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_activity-proto"></a>
<p align="right"><a href="#top">Top</a></p>
## store/activity.proto
<a name="slash-store-ActivityShorcutCreatePayload"></a>
### ActivityShorcutCreatePayload
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| shortcut_id | [int32](#int32) | | |
<a name="store_common-proto"></a>
<p align="right"><a href="#top">Top</a></p>
@ -118,6 +159,82 @@
<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 |

View File

@ -0,0 +1,153 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc (unknown)
// source: store/activity.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 ActivityShorcutCreatePayload struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ShortcutId int32 `protobuf:"varint,1,opt,name=shortcut_id,json=shortcutId,proto3" json:"shortcut_id,omitempty"`
}
func (x *ActivityShorcutCreatePayload) Reset() {
*x = ActivityShorcutCreatePayload{}
if protoimpl.UnsafeEnabled {
mi := &file_store_activity_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ActivityShorcutCreatePayload) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ActivityShorcutCreatePayload) ProtoMessage() {}
func (x *ActivityShorcutCreatePayload) ProtoReflect() protoreflect.Message {
mi := &file_store_activity_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 ActivityShorcutCreatePayload.ProtoReflect.Descriptor instead.
func (*ActivityShorcutCreatePayload) Descriptor() ([]byte, []int) {
return file_store_activity_proto_rawDescGZIP(), []int{0}
}
func (x *ActivityShorcutCreatePayload) GetShortcutId() int32 {
if x != nil {
return x.ShortcutId
}
return 0
}
var File_store_activity_proto protoreflect.FileDescriptor
var file_store_activity_proto_rawDesc = []byte{
0x0a, 0x14, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x22, 0x3f, 0x0a, 0x1c, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x53,
0x68, 0x6f, 0x72, 0x63, 0x75, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c,
0x6f, 0x61, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63,
0x75, 0x74, 0x49, 0x64, 0x42, 0x97, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x6c, 0x61,
0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x0d, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69,
0x74, 0x79, 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_activity_proto_rawDescOnce sync.Once
file_store_activity_proto_rawDescData = file_store_activity_proto_rawDesc
)
func file_store_activity_proto_rawDescGZIP() []byte {
file_store_activity_proto_rawDescOnce.Do(func() {
file_store_activity_proto_rawDescData = protoimpl.X.CompressGZIP(file_store_activity_proto_rawDescData)
})
return file_store_activity_proto_rawDescData
}
var file_store_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_store_activity_proto_goTypes = []interface{}{
(*ActivityShorcutCreatePayload)(nil), // 0: slash.store.ActivityShorcutCreatePayload
}
var file_store_activity_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_activity_proto_init() }
func file_store_activity_proto_init() {
if File_store_activity_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_store_activity_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ActivityShorcutCreatePayload); 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_activity_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_store_activity_proto_goTypes,
DependencyIndexes: file_store_activity_proto_depIdxs,
MessageInfos: file_store_activity_proto_msgTypes,
}.Build()
File_store_activity_proto = out.File
file_store_activity_proto_rawDesc = nil
file_store_activity_proto_goTypes = nil
file_store_activity_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
}

View File

@ -0,0 +1,9 @@
syntax = "proto3";
package slash.store;
option go_package = "gen/store";
message ActivityShorcutCreatePayload {
int32 shortcut_id = 1;
}

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.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
resources/logo-pixel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
resources/logo128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -5,7 +5,7 @@ tmp_dir = ".air"
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

59
server/resource.go Normal file
View File

@ -0,0 +1,59 @@
package server
import (
"bytes"
"fmt"
"net/http"
"os"
"github.com/boojack/slash/server/profile"
"github.com/boojack/slash/store"
"github.com/h2non/filetype"
"github.com/labstack/echo/v4"
)
type ResourceService struct {
Profile *profile.Profile
Store *store.Store
}
func NewResourceService(profile *profile.Profile, store *store.Store) *ResourceService {
return &ResourceService{
Profile: profile,
Store: store,
}
}
// Register registers the resource service to the echo server.
func (s *ResourceService) Register(g *echo.Group) {
g.GET("/resources/:id", func(c echo.Context) error {
ctx := c.Request().Context()
resourceID := c.Param("resourceId")
resourceRelativePathSetting, err := s.Store.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{
Key: store.WorkspaceResourceRelativePath,
})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "failed to workspace resource relative path setting").SetInternal(err)
}
if resourceRelativePathSetting == nil || resourceRelativePathSetting.Value == "" {
return echo.NewHTTPError(http.StatusBadRequest, "found no workspace resource relative path setting")
}
resourceRelativePath := resourceRelativePathSetting.Value
resourcePath := fmt.Sprintf("%s/%s", resourceRelativePath, resourceID)
buf, err := os.ReadFile(resourcePath)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to read the local resource: %s", resourcePath)).SetInternal(err)
}
kind, err := filetype.Match(buf)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to match the local resource: %s", resourcePath)).SetInternal(err)
}
resourceMimeType := kind.MIME.Value
c.Response().Writer.Header().Set(echo.HeaderCacheControl, "max-age=31536000, immutable")
c.Response().Writer.Header().Set(echo.HeaderContentSecurityPolicy, "default-src 'self'")
c.Response().Writer.Header().Set("Content-Disposition", fmt.Sprintf(`filename="%s"`, resourceID))
return c.Stream(http.StatusOK, resourceMimeType, bytes.NewReader(buf))
})
}

View File

@ -76,6 +76,10 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
return nil, fmt.Errorf("failed to register gRPC gateway: %w", err)
}
// Register resource service.
resourceService := NewResourceService(profile, store)
resourceService.Register(rootGroup)
return s, nil
}
@ -111,6 +115,10 @@ func (s *Server) Shutdown(ctx context.Context) {
fmt.Printf("server stopped properly\n")
}
func (s *Server) GetEcho() *echo.Echo {
return s.e
}
func (s *Server) getSystemSecretSessionName(ctx context.Context) (string, error) {
secretSessionNameValue, err := s.Store.GetWorkspaceSetting(ctx, &store.FindWorkspaceSetting{
Key: store.WorkspaceDisallowSignUp,

View File

@ -9,10 +9,10 @@ import (
// Version is the service current released version.
// Semantic versioning: https://semver.org/
var Version = "0.4.0"
var Version = "0.4.3"
// DevVersion is the service current development version.
var DevVersion = "0.4.0"
var DevVersion = "0.4.3"
func GetCurrentVersion(mode string) string {
if mode == "dev" || mode == "demo" {

View File

@ -59,3 +59,12 @@ CREATE TABLE activity (
level TEXT NOT NULL CHECK (level IN ('INFO', 'WARN', 'ERROR')) DEFAULT 'INFO',
payload TEXT NOT NULL DEFAULT '{}'
);
-- idp
CREATE TABLE idp (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
identifier_filter TEXT NOT NULL DEFAULT '',
config TEXT NOT NULL DEFAULT '{}'
);

View File

@ -0,0 +1,10 @@
DROP TABLE IF EXISTS idp;
-- idp
CREATE TABLE idp (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
identifier_filter TEXT NOT NULL DEFAULT '',
config TEXT NOT NULL DEFAULT '{}'
);

View File

@ -59,3 +59,12 @@ CREATE TABLE activity (
level TEXT NOT NULL CHECK (level IN ('INFO', 'WARN', 'ERROR')) DEFAULT 'INFO',
payload TEXT NOT NULL DEFAULT '{}'
);
-- idp
CREATE TABLE idp (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
identifier_filter TEXT NOT NULL DEFAULT '',
config TEXT NOT NULL DEFAULT '{}'
);

View File

@ -3,21 +3,19 @@ package store
import (
"context"
"database/sql"
"errors"
"strings"
)
type UserSetting struct {
UserID int32
Key string
Value string
}
storepb "github.com/boojack/slash/proto/gen/store"
"google.golang.org/protobuf/encoding/protojson"
)
type FindUserSetting struct {
UserID *int32
Key string
Key storepb.UserSettingKey
}
func (s *Store) UpsertUserSetting(ctx context.Context, upsert *UserSetting) (*UserSetting, error) {
func (s *Store) UpsertUserSetting(ctx context.Context, upsert *storepb.UserSetting) (*storepb.UserSetting, error) {
stmt := `
INSERT INTO user_setting (
user_id, key, value
@ -26,20 +24,31 @@ func (s *Store) UpsertUserSetting(ctx context.Context, upsert *UserSetting) (*Us
ON CONFLICT(user_id, key) DO UPDATE
SET value = EXCLUDED.value
`
if _, err := s.db.ExecContext(ctx, stmt, upsert.UserID, upsert.Key, upsert.Value); err != nil {
var valueString string
if upsert.Key == storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS {
valueBytes, err := protojson.Marshal(upsert.GetAccessTokensUserSetting())
if err != nil {
return nil, err
}
valueString = string(valueBytes)
} else {
return nil, errors.New("invalid user setting key")
}
if _, err := s.db.ExecContext(ctx, stmt, upsert.UserId, upsert.Key.String(), valueString); err != nil {
return nil, err
}
userSettingMessage := upsert
s.userSettingCache.Store(getUserSettingCacheKey(userSettingMessage.UserID, userSettingMessage.Key), userSettingMessage)
s.userSettingCache.Store(getUserSettingCacheKey(userSettingMessage.UserId, userSettingMessage.Key.String()), userSettingMessage)
return userSettingMessage, nil
}
func (s *Store) ListUserSettings(ctx context.Context, find *FindUserSetting) ([]*UserSetting, error) {
func (s *Store) ListUserSettings(ctx context.Context, find *FindUserSetting) ([]*storepb.UserSetting, error) {
where, args := []string{"1 = 1"}, []any{}
if v := find.Key; v != "" {
where, args = append(where, "key = ?"), append(args, v)
if v := find.Key; v != storepb.UserSettingKey_USER_SETTING_KEY_UNSPECIFIED {
where, args = append(where, "key = ?"), append(args, v.String())
}
if v := find.UserID; v != nil {
where, args = append(where, "user_id = ?"), append(args, *find.UserID)
@ -58,16 +67,27 @@ func (s *Store) ListUserSettings(ctx context.Context, find *FindUserSetting) ([]
}
defer rows.Close()
userSettingList := make([]*UserSetting, 0)
userSettingList := make([]*storepb.UserSetting, 0)
for rows.Next() {
userSetting := &UserSetting{}
userSetting := &storepb.UserSetting{}
var keyString, valueString string
if err := rows.Scan(
&userSetting.UserID,
&userSetting.Key,
&userSetting.Value,
&userSetting.UserId,
&keyString,
&valueString,
); err != nil {
return nil, err
}
userSetting.Key = storepb.UserSettingKey(storepb.UserSettingKey_value[keyString])
if userSetting.Key == storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS {
accessTokensUserSetting := &storepb.AccessTokensUserSetting{}
if err := protojson.Unmarshal([]byte(valueString), accessTokensUserSetting); err != nil {
return nil, err
}
userSetting.Value = &storepb.UserSetting_AccessTokensUserSetting{
AccessTokensUserSetting: accessTokensUserSetting,
}
}
userSettingList = append(userSettingList, userSetting)
}
@ -76,15 +96,15 @@ func (s *Store) ListUserSettings(ctx context.Context, find *FindUserSetting) ([]
}
for _, userSetting := range userSettingList {
s.userSettingCache.Store(getUserSettingCacheKey(userSetting.UserID, userSetting.Key), userSetting)
s.userSettingCache.Store(getUserSettingCacheKey(userSetting.UserId, userSetting.Key.String()), userSetting)
}
return userSettingList, nil
}
func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSetting) (*UserSetting, error) {
if find.UserID != nil && find.Key != "" {
if cache, ok := s.userSettingCache.Load(getUserSettingCacheKey(*find.UserID, find.Key)); ok {
return cache.(*UserSetting), nil
func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSetting) (*storepb.UserSetting, error) {
if find.UserID != nil && find.Key != storepb.UserSettingKey_USER_SETTING_KEY_UNSPECIFIED {
if cache, ok := s.userSettingCache.Load(getUserSettingCacheKey(*find.UserID, find.Key.String())); ok {
return cache.(*storepb.UserSetting), nil
}
}
@ -97,9 +117,9 @@ func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSetting) (*Use
return nil, nil
}
userSettingMessage := list[0]
s.userSettingCache.Store(getUserSettingCacheKey(userSettingMessage.UserID, userSettingMessage.Key), userSettingMessage)
return userSettingMessage, nil
userSetting := list[0]
s.userSettingCache.Store(getUserSettingCacheKey(userSetting.UserId, userSetting.Key.String()), userSetting)
return userSetting, nil
}
func vacuumUserSetting(ctx context.Context, tx *sql.Tx) error {
@ -120,3 +140,20 @@ func vacuumUserSetting(ctx context.Context, tx *sql.Tx) error {
return nil
}
// GetUserAccessTokens returns the access tokens of the user.
func (s *Store) GetUserAccessTokens(ctx context.Context, userID int32) ([]*storepb.AccessTokensUserSetting_AccessToken, error) {
userSetting, err := s.GetUserSetting(ctx, &FindUserSetting{
UserID: &userID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
})
if err != nil {
return nil, err
}
if userSetting == nil {
return []*storepb.AccessTokensUserSetting_AccessToken{}, nil
}
accessTokensUserSetting := userSetting.GetAccessTokensUserSetting()
return accessTokensUserSetting.AccessTokens, nil
}

View File

@ -8,10 +8,12 @@ import (
type WorkspaceSettingKey string
const (
// WorkspaceDisallowSignUp is the key type for disallow sign up in workspace level.
WorkspaceDisallowSignUp WorkspaceSettingKey = "disallow-signup"
// WorkspaceSecretSessionName is the key type for secret session name.
WorkspaceSecretSessionName WorkspaceSettingKey = "secret-session-name"
// WorkspaceDisallowSignUp is the key type for disallow sign up in workspace level.
WorkspaceDisallowSignUp WorkspaceSettingKey = "disallow-signup"
// WorkspaceResourceRelativePath is the key type for resource relative path.
WorkspaceResourceRelativePath WorkspaceSettingKey = "resource-relative-path"
)
// String returns the string format of WorkspaceSettingKey type.

93
test/server/auth_test.go Normal file
View File

@ -0,0 +1,93 @@
package testserver
import (
"bytes"
"context"
"encoding/json"
"testing"
apiv1 "github.com/boojack/slash/api/v1"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
func TestAuthServer(t *testing.T) {
ctx := context.Background()
s, err := NewTestingServer(ctx, t)
require.NoError(t, err)
defer s.Shutdown(ctx)
signup := &apiv1.SignUpRequest{
Email: "slash@yourselfhosted.com",
Password: "testpassword",
}
user, err := s.postAuthSignUp(signup)
require.NoError(t, err)
require.Equal(t, signup.Email, user.Email)
signin := &apiv1.SignInRequest{
Email: "slash@yourselfhosted.com",
Password: "testpassword",
}
user, err = s.postAuthSignIn(signin)
require.NoError(t, err)
require.Equal(t, signup.Email, user.Email)
err = s.postLogout()
require.NoError(t, err)
}
func (s *TestingServer) postAuthSignUp(signup *apiv1.SignUpRequest) (*apiv1.User, error) {
rawData, err := json.Marshal(&signup)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal signup")
}
reader := bytes.NewReader(rawData)
body, err := s.post("/api/v1/auth/signup", reader, nil)
if err != nil {
return nil, errors.Wrap(err, "fail to post request")
}
buf := &bytes.Buffer{}
_, err = buf.ReadFrom(body)
if err != nil {
return nil, errors.Wrap(err, "fail to read response body")
}
user := &apiv1.User{}
if err = json.Unmarshal(buf.Bytes(), user); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal post signup response")
}
return user, nil
}
func (s *TestingServer) postAuthSignIn(signip *apiv1.SignInRequest) (*apiv1.User, error) {
rawData, err := json.Marshal(&signip)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal signin")
}
reader := bytes.NewReader(rawData)
body, err := s.post("/api/v1/auth/signin", reader, nil)
if err != nil {
return nil, errors.Wrap(err, "fail to post request")
}
buf := &bytes.Buffer{}
_, err = buf.ReadFrom(body)
if err != nil {
return nil, errors.Wrap(err, "fail to read response body")
}
user := &apiv1.User{}
if err = json.Unmarshal(buf.Bytes(), user); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal post signin response")
}
return user, nil
}
func (s *TestingServer) postLogout() error {
_, err := s.post("/api/v1/auth/logout", nil, nil)
if err != nil {
return errors.Wrap(err, "fail to post request")
}
return nil
}

178
test/server/server.go Normal file
View File

@ -0,0 +1,178 @@
package testserver
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"testing"
"time"
"github.com/boojack/slash/api/auth"
"github.com/boojack/slash/server"
"github.com/boojack/slash/server/profile"
"github.com/boojack/slash/store"
"github.com/boojack/slash/store/db"
"github.com/boojack/slash/test"
"github.com/pkg/errors"
// sqlite driver.
_ "modernc.org/sqlite"
)
type TestingServer struct {
server *server.Server
client *http.Client
profile *profile.Profile
cookie string
}
func NewTestingServer(ctx context.Context, t *testing.T) (*TestingServer, error) {
profile := test.GetTestingProfile(t)
db := db.NewDB(profile)
if err := db.Open(ctx); err != nil {
return nil, errors.Wrap(err, "failed to open db")
}
store := store.New(db.DBInstance, profile)
server, err := server.NewServer(ctx, profile, store)
if err != nil {
return nil, errors.Wrap(err, "failed to create server")
}
s := &TestingServer{
server: server,
client: &http.Client{},
profile: profile,
cookie: "",
}
errChan := make(chan error, 1)
go func() {
if err := s.server.Start(ctx); err != nil {
if err != http.ErrServerClosed {
errChan <- errors.Wrap(err, "failed to run main server")
}
}
}()
if err := s.waitForServerStart(errChan); err != nil {
return nil, errors.Wrap(err, "failed to start server")
}
return s, nil
}
func (s *TestingServer) Shutdown(ctx context.Context) {
s.server.Shutdown(ctx)
}
func (s *TestingServer) waitForServerStart(errChan <-chan error) error {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if s == nil {
continue
}
e := s.server.GetEcho()
if e == nil {
continue
}
addr := e.ListenerAddr()
if addr != nil && strings.Contains(addr.String(), ":") {
return nil // was started
}
case err := <-errChan:
if err == http.ErrServerClosed {
return nil
}
return err
}
}
}
func (s *TestingServer) request(method, uri string, body io.Reader, params, header map[string]string) (io.ReadCloser, error) {
fullURL := fmt.Sprintf("http://localhost:%d%s", s.profile.Port, uri)
req, err := http.NewRequest(method, fullURL, body)
if err != nil {
return nil, errors.Wrapf(err, "fail to create a new %s request(%q)", method, fullURL)
}
for k, v := range header {
req.Header.Set(k, v)
}
q := url.Values{}
for k, v := range params {
q.Add(k, v)
}
if len(q) > 0 {
req.URL.RawQuery = q.Encode()
}
resp, err := s.client.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "fail to send a %s request(%q)", method, fullURL)
}
if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "failed to read http response body")
}
return nil, errors.Errorf("http response error code %v body %q", resp.StatusCode, string(body))
}
if method == "POST" {
if strings.Contains(uri, "/api/v1/auth/signin") || strings.Contains(uri, "/api/v1/auth/signup") {
cookie := ""
h := resp.Header.Get("Set-Cookie")
parts := strings.Split(h, "; ")
for _, p := range parts {
if strings.HasPrefix(p, fmt.Sprintf("%s=", auth.AccessTokenCookieName)) {
cookie = p
break
}
}
if cookie == "" {
return nil, errors.Errorf("unable to find access token in the login response headers")
}
s.cookie = cookie
} else if strings.Contains(uri, "/api/v1/auth/logout") {
s.cookie = ""
}
}
return resp.Body, nil
}
// get sends a GET client request.
func (s *TestingServer) get(url string, params map[string]string) (io.ReadCloser, error) {
return s.request("GET", url, nil, params, map[string]string{
"Cookie": s.cookie,
})
}
// post sends a POST client request.
func (s *TestingServer) post(url string, body io.Reader, params map[string]string) (io.ReadCloser, error) {
return s.request("POST", url, body, params, map[string]string{
"Cookie": s.cookie,
})
}
// patch sends a PATCH client request.
func (s *TestingServer) patch(url string, body io.Reader, params map[string]string) (io.ReadCloser, error) {
return s.request("PATCH", url, body, params, map[string]string{
"Cookie": s.cookie,
})
}
// delete sends a DELETE client request.
func (s *TestingServer) delete(url string, params map[string]string) (io.ReadCloser, error) {
return s.request("DELETE", url, nil, params, map[string]string{
"Cookie": s.cookie,
})
}

View File

@ -0,0 +1,72 @@
package testserver
import (
"bytes"
"context"
"encoding/json"
"fmt"
"testing"
apiv1 "github.com/boojack/slash/api/v1"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
func TestShortcutServer(t *testing.T) {
ctx := context.Background()
s, err := NewTestingServer(ctx, t)
require.NoError(t, err)
defer s.Shutdown(ctx)
signup := &apiv1.SignUpRequest{
Email: "slash@yourselfhosted.com",
Password: "testpassword",
}
user, err := s.postAuthSignUp(signup)
require.NoError(t, err)
require.Equal(t, signup.Email, user.Email)
user, err = s.getCurrentUser()
require.NoError(t, err)
require.Equal(t, signup.Email, user.Email)
shortcutCreate := &apiv1.CreateShortcutRequest{
Name: "test",
Link: "https://google.com",
Visibility: apiv1.VisibilityPublic,
Tags: []string{},
}
shortcut, err := s.postShortcutCreate(shortcutCreate)
require.NoError(t, err)
require.Equal(t, shortcutCreate.Name, shortcut.Name)
require.Equal(t, shortcutCreate.Link, shortcut.Link)
err = s.deleteShortcut(shortcut.ID)
require.NoError(t, err)
}
func (s *TestingServer) postShortcutCreate(request *apiv1.CreateShortcutRequest) (*apiv1.Shortcut, error) {
rawData, err := json.Marshal(&request)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal shortcut create")
}
reader := bytes.NewReader(rawData)
body, err := s.post("/api/v1/shortcut", reader, nil)
if err != nil {
return nil, errors.Wrap(err, "fail to post request")
}
buf := &bytes.Buffer{}
_, err = buf.ReadFrom(body)
if err != nil {
return nil, errors.Wrap(err, "fail to read response body")
}
shortcut := &apiv1.Shortcut{}
if err = json.Unmarshal(buf.Bytes(), &shortcut); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal post shortcut response")
}
return shortcut, nil
}
func (s *TestingServer) deleteShortcut(shortcutID int32) error {
_, err := s.delete(fmt.Sprintf("/api/v1/shortcut/%d", shortcutID), nil)
return err
}

103
test/server/user_test.go Normal file
View File

@ -0,0 +1,103 @@
package testserver
import (
"bytes"
"context"
"encoding/json"
"fmt"
"testing"
apiv1 "github.com/boojack/slash/api/v1"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
func TestUserServer(t *testing.T) {
ctx := context.Background()
s, err := NewTestingServer(ctx, t)
require.NoError(t, err)
defer s.Shutdown(ctx)
signup := &apiv1.SignUpRequest{
Email: "slash@yourselfhosted.com",
Password: "testpassword",
}
user, err := s.postAuthSignUp(signup)
require.NoError(t, err)
require.Equal(t, signup.Email, user.Email)
user, err = s.getCurrentUser()
require.NoError(t, err)
require.Equal(t, signup.Email, user.Email)
user, err = s.getUserByID(user.ID)
require.NoError(t, err)
require.Equal(t, signup.Email, user.Email)
newEmail := "test@usermemos.com"
userPatch := &apiv1.PatchUserRequest{
Email: &newEmail,
}
user, err = s.patchUser(user.ID, userPatch)
require.NoError(t, err)
require.Equal(t, newEmail, user.Email)
}
func (s *TestingServer) getCurrentUser() (*apiv1.User, error) {
body, err := s.get("/api/v1/user/me", nil)
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
_, err = buf.ReadFrom(body)
if err != nil {
return nil, errors.Wrap(err, "fail to read response body")
}
user := &apiv1.User{}
if err = json.Unmarshal(buf.Bytes(), &user); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal get user response")
}
return user, nil
}
func (s *TestingServer) getUserByID(userID int32) (*apiv1.User, error) {
body, err := s.get(fmt.Sprintf("/api/v1/user/%d", userID), nil)
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
_, err = buf.ReadFrom(body)
if err != nil {
return nil, errors.Wrap(err, "fail to read response body")
}
user := &apiv1.User{}
if err = json.Unmarshal(buf.Bytes(), &user); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal get user response")
}
return user, nil
}
func (s *TestingServer) patchUser(userID int32, request *apiv1.PatchUserRequest) (*apiv1.User, error) {
rawData, err := json.Marshal(&request)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal request")
}
reader := bytes.NewReader(rawData)
body, err := s.patch(fmt.Sprintf("/api/v1/user/%d", userID), reader, nil)
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
_, err = buf.ReadFrom(body)
if err != nil {
return nil, errors.Wrap(err, "fail to read response body")
}
user := &apiv1.User{}
if err = json.Unmarshal(buf.Bytes(), user); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal patch user response")
}
return user, nil
}

View File

@ -0,0 +1,73 @@
package teststore
import (
"context"
"testing"
storepb "github.com/boojack/slash/proto/gen/store"
"github.com/boojack/slash/store"
"github.com/stretchr/testify/require"
)
func TestUserSettingStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingAdminUser(ctx, ts)
require.NoError(t, err)
userSettings, err := ts.ListUserSettings(ctx, &store.FindUserSetting{
UserID: &user.ID,
})
require.NoError(t, err)
require.Equal(t, 0, len(userSettings))
accessTokensUserSetting, err := ts.UpsertUserSetting(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
Value: &storepb.UserSetting_AccessTokensUserSetting{
AccessTokensUserSetting: &storepb.AccessTokensUserSetting{
AccessTokens: []*storepb.AccessTokensUserSetting_AccessToken{
{
AccessToken: "test_access_token",
},
},
},
},
})
require.NoError(t, err)
require.NotNil(t, accessTokensUserSetting)
require.Equal(t, storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS, accessTokensUserSetting.Key)
require.Equal(t, user.ID, accessTokensUserSetting.UserId)
require.Equal(t, 1, len(accessTokensUserSetting.GetAccessTokensUserSetting().AccessTokens))
userSettings, err = ts.ListUserSettings(ctx, &store.FindUserSetting{
UserID: &user.ID,
})
require.NoError(t, err)
require.Equal(t, 1, len(userSettings))
require.Equal(t, accessTokensUserSetting, userSettings[0])
accessTokensUserSetting, err = ts.GetUserSetting(ctx, &store.FindUserSetting{
UserID: &user.ID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
})
require.NoError(t, err)
require.NotNil(t, accessTokensUserSetting)
require.Equal(t, 1, len(accessTokensUserSetting.GetAccessTokensUserSetting().AccessTokens))
require.Equal(t, "test_access_token", accessTokensUserSetting.GetAccessTokensUserSetting().AccessTokens[0].AccessToken)
accessTokensUserSetting, err = ts.UpsertUserSetting(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
Value: &storepb.UserSetting_AccessTokensUserSetting{
AccessTokensUserSetting: &storepb.AccessTokensUserSetting{
AccessTokens: []*storepb.AccessTokensUserSetting_AccessToken{
{
AccessToken: "test_access_token",
},
{
AccessToken: "test_access_token2",
},
},
},
},
})
require.NoError(t, err)
require.NotNil(t, accessTokensUserSetting)
require.Equal(t, 2, len(accessTokensUserSetting.GetAccessTokensUserSetting().AccessTokens))
}

View File

@ -1,4 +1,4 @@
package tests
package test
import (
"fmt"

View File

@ -1,8 +0,0 @@
{
"printWidth": 140,
"useTabs": false,
"semi": true,
"singleQuote": false,
"plugins": ["@ianvs/prettier-plugin-sort-imports"],
"importOrder": ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]", ".less$"]
}

8
web/.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).+)", "^[./]", "^(.+).less"],
};

View File

@ -8,9 +8,10 @@
"lint-fix": "eslint --ext .js,.ts,.tsx, src --fix"
},
"dependencies": {
"@bufbuild/protobuf": "^1.3.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/joy": "5.0.0-beta.0",
"@mui/joy": "5.0.0-beta.2",
"@reduxjs/toolkit": "^1.9.5",
"axios": "^0.27.2",
"classnames": "^2.3.2",
@ -30,7 +31,7 @@
"zustand": "^4.3.8"
},
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.1.0",
"@trivago/prettier-plugin-sort-imports": "^4.1.0",
"@types/lodash-es": "^4.17.5",
"@types/node": "^20.3.1",
"@types/react": "^18.2.18",
@ -44,8 +45,8 @@
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.27.1",
"postcss": "^8.4.21",
"prettier": "2.5.1",
"prettier": "2.6.2",
"typescript": "^5.0.4",
"vite": "^4.2.1"
"vite": "^4.2.3"
}
}

256
web/pnpm-lock.yaml generated
View File

@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
'@bufbuild/protobuf':
specifier: ^1.3.0
version: 1.3.0
'@emotion/react':
specifier: ^11.11.1
version: 11.11.1(@types/react@18.2.18)(react@18.2.0)
@ -12,8 +15,8 @@ dependencies:
specifier: ^11.11.0
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.18)(react@18.2.0)
'@mui/joy':
specifier: 5.0.0-beta.0
version: 5.0.0-beta.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0)
specifier: 5.0.0-beta.2
version: 5.0.0-beta.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0)
'@reduxjs/toolkit':
specifier: ^1.9.5
version: 1.9.5(react-redux@8.0.2)(react@18.2.0)
@ -67,9 +70,9 @@ dependencies:
version: 4.3.8(react@18.2.0)
devDependencies:
'@ianvs/prettier-plugin-sort-imports':
'@trivago/prettier-plugin-sort-imports':
specifier: ^4.1.0
version: 4.1.0(prettier@2.5.1)
version: 4.1.0(prettier@2.6.2)
'@types/lodash-es':
specifier: ^4.17.5
version: 4.17.5
@ -90,7 +93,7 @@ devDependencies:
version: 6.2.0(eslint@8.46.0)(typescript@5.0.4)
'@vitejs/plugin-react-swc':
specifier: ^3.0.0
version: 3.0.0(vite@4.2.1)
version: 3.0.0(vite@4.2.3)
autoprefixer:
specifier: ^10.4.2
version: 10.4.2(postcss@8.4.21)
@ -102,7 +105,7 @@ devDependencies:
version: 8.3.0(eslint@8.46.0)
eslint-plugin-prettier:
specifier: ^4.0.0
version: 4.0.0(eslint-config-prettier@8.3.0)(eslint@8.46.0)(prettier@2.5.1)
version: 4.0.0(eslint-config-prettier@8.3.0)(eslint@8.46.0)(prettier@2.6.2)
eslint-plugin-react:
specifier: ^7.27.1
version: 7.27.1(eslint@8.46.0)
@ -110,14 +113,14 @@ devDependencies:
specifier: ^8.4.21
version: 8.4.21
prettier:
specifier: 2.5.1
version: 2.5.1
specifier: 2.6.2
version: 2.6.2
typescript:
specifier: ^5.0.4
version: 5.0.4
vite:
specifier: ^4.2.1
version: 4.2.1(@types/node@20.3.1)
specifier: ^4.2.3
version: 4.2.3(@types/node@20.3.1)
packages:
@ -131,46 +134,19 @@ packages:
engines: {node: '>=10'}
dev: false
/@ampproject/remapping@2.2.1:
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/gen-mapping': 0.3.3
'@jridgewell/trace-mapping': 0.3.18
dev: true
/@babel/code-frame@7.22.5:
resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/highlight': 7.22.5
/@babel/compat-data@7.22.9:
resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/core@7.22.9:
resolution: {integrity: sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==}
/@babel/generator@7.17.7:
resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==}
engines: {node: '>=6.9.0'}
dependencies:
'@ampproject/remapping': 2.2.1
'@babel/code-frame': 7.22.5
'@babel/generator': 7.22.9
'@babel/helper-compilation-targets': 7.22.9(@babel/core@7.22.9)
'@babel/helper-module-transforms': 7.22.9(@babel/core@7.22.9)
'@babel/helpers': 7.22.6
'@babel/parser': 7.22.7
'@babel/template': 7.22.5
'@babel/traverse': 7.22.8
'@babel/types': 7.22.5
convert-source-map: 1.9.0
debug: 4.3.4
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
transitivePeerDependencies:
- supports-color
jsesc: 2.5.2
source-map: 0.5.7
dev: true
/@babel/generator@7.22.9:
@ -183,20 +159,6 @@ packages:
jsesc: 2.5.2
dev: true
/@babel/helper-compilation-targets@7.22.9(@babel/core@7.22.9):
resolution: {integrity: sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
'@babel/compat-data': 7.22.9
'@babel/core': 7.22.9
'@babel/helper-validator-option': 7.22.5
browserslist: 4.21.10
lru-cache: 5.1.1
semver: 6.3.1
dev: true
/@babel/helper-environment-visitor@7.22.5:
resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==}
engines: {node: '>=6.9.0'}
@ -222,27 +184,7 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.22.5
/@babel/helper-module-transforms@7.22.9(@babel/core@7.22.9):
resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
'@babel/core': 7.22.9
'@babel/helper-environment-visitor': 7.22.5
'@babel/helper-module-imports': 7.22.5
'@babel/helper-simple-access': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6
'@babel/helper-validator-identifier': 7.22.5
dev: true
/@babel/helper-simple-access@7.22.5:
resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.22.5
dev: true
dev: false
/@babel/helper-split-export-declaration@7.22.6:
resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
@ -259,22 +201,6 @@ packages:
resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==}
engines: {node: '>=6.9.0'}
/@babel/helper-validator-option@7.22.5:
resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helpers@7.22.6:
resolution: {integrity: sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.22.5
'@babel/traverse': 7.22.8
'@babel/types': 7.22.5
transitivePeerDependencies:
- supports-color
dev: true
/@babel/highlight@7.22.5:
resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==}
engines: {node: '>=6.9.0'}
@ -307,8 +233,8 @@ packages:
'@babel/types': 7.22.5
dev: true
/@babel/traverse@7.22.8:
resolution: {integrity: sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==}
/@babel/traverse@7.17.3:
resolution: {integrity: sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.22.5
@ -325,6 +251,14 @@ packages:
- supports-color
dev: true
/@babel/types@7.17.0:
resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-validator-identifier': 7.22.5
to-fast-properties: 2.0.0
dev: true
/@babel/types@7.22.5:
resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==}
engines: {node: '>=6.9.0'}
@ -333,6 +267,10 @@ packages:
'@babel/helper-validator-identifier': 7.22.5
to-fast-properties: 2.0.0
/@bufbuild/protobuf@1.3.0:
resolution: {integrity: sha512-G372ods0pLt46yxVRsnP/e2btVPuuzArcMPFpIDeIwiGPuuglEs9y75iG0HMvZgncsj5TvbYRWqbVyOe3PLCWQ==}
dev: false
/@emotion/babel-plugin@11.11.0:
resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==}
dependencies:
@ -704,26 +642,6 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true
/@ianvs/prettier-plugin-sort-imports@4.1.0(prettier@2.5.1):
resolution: {integrity: sha512-IAXeTLU24k6mRPa6mFbW1qZJ/j0m3OeH44wyijWyr+YqqdNtBnfHxAntOAATS9iDfrT01NesKGsdzqnXdDQa/A==}
peerDependencies:
'@vue/compiler-sfc': '>=3.0.0'
prettier: 2 || 3
peerDependenciesMeta:
'@vue/compiler-sfc':
optional: true
dependencies:
'@babel/core': 7.22.9
'@babel/generator': 7.22.9
'@babel/parser': 7.22.7
'@babel/traverse': 7.22.8
'@babel/types': 7.22.5
prettier: 2.5.1
semver: 7.5.4
transitivePeerDependencies:
- supports-color
dev: true
/@jridgewell/gen-mapping@0.3.3:
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
engines: {node: '>=6.0.0'}
@ -752,8 +670,8 @@ packages:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
/@mui/base@5.0.0-beta.9(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-gm6gnPnc/lS5Z3neH0iuOrK7IbS02+oh6KsMtXYLhI6bJpHs+PNWFsBmISx7x4FSPVJZvZkb8Bw6pEXpIMFt7Q==}
/@mui/base@5.0.0-beta.11(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-FdKZGPd8qmC3ZNke7CNhzcEgToc02M6WYZc9hcBsNQ17bgAd3s9F//1bDDYgMVBYxDM71V0sv/hBHlOY4I1ZVA==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@ -766,7 +684,7 @@ packages:
'@babel/runtime': 7.22.6
'@emotion/is-prop-valid': 1.2.1
'@mui/types': 7.2.4(@types/react@18.2.18)
'@mui/utils': 5.14.3(react@18.2.0)
'@mui/utils': 5.14.5(react@18.2.0)
'@popperjs/core': 2.11.8
'@types/react': 18.2.18
clsx: 2.0.0
@ -776,12 +694,12 @@ packages:
react-is: 18.2.0
dev: false
/@mui/core-downloads-tracker@5.14.3:
resolution: {integrity: sha512-QxvrcDqphZoXRjsAmCaQylmWjC/8/qKWwIde1MJMna5YIst3R9O0qhKRPu36/OE2d8AeTbCVjRcRvNqhhW8jyg==}
/@mui/core-downloads-tracker@5.14.5:
resolution: {integrity: sha512-+wpGH1USwPcKMFPMvXqYPC6fEvhxM3FzxC8lyDiNK/imLyyJ6y2DPb1Oue7OGIKJWBmYBqrWWtfovrxd1aJHTA==}
dev: false
/@mui/joy@5.0.0-beta.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-WDGlbEMqXPmuwgUPEgJEPeOUJD46WragfPqTjoWEp+//0iE8kcn+YfFVgsoY31uID5UwcFWQRupxui872slANA==}
/@mui/joy@5.0.0-beta.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-5NfZcOYufTOSXh0b34YZzF1CHwuHf07cgSNdyn3WUUwg67oyNOPKhaskttX6aSp0j8Rf8OpKzd+7Ni6Em0jPgQ==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@ -800,11 +718,11 @@ packages:
'@babel/runtime': 7.22.6
'@emotion/react': 11.11.1(@types/react@18.2.18)(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.18)(react@18.2.0)
'@mui/base': 5.0.0-beta.9(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0)
'@mui/core-downloads-tracker': 5.14.3
'@mui/system': 5.14.3(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.18)(react@18.2.0)
'@mui/base': 5.0.0-beta.11(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0)
'@mui/core-downloads-tracker': 5.14.5
'@mui/system': 5.14.5(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.18)(react@18.2.0)
'@mui/types': 7.2.4(@types/react@18.2.18)
'@mui/utils': 5.14.3(react@18.2.0)
'@mui/utils': 5.14.5(react@18.2.0)
'@types/react': 18.2.18
clsx: 2.0.0
csstype: 3.1.2
@ -814,8 +732,8 @@ packages:
react-is: 18.2.0
dev: false
/@mui/private-theming@5.13.7(@types/react@18.2.18)(react@18.2.0):
resolution: {integrity: sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA==}
/@mui/private-theming@5.14.5(@types/react@18.2.18)(react@18.2.0):
resolution: {integrity: sha512-cC4C5RrpXpDaaZyH9QwmPhRLgz+f2SYbOty3cPkk4qPSOSfif2ZEcDD9HTENKDDd9deB+xkPKzzZhi8cxIx8Ig==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@ -825,7 +743,7 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.22.6
'@mui/utils': 5.14.3(react@18.2.0)
'@mui/utils': 5.14.5(react@18.2.0)
'@types/react': 18.2.18
prop-types: 15.8.1
react: 18.2.0
@ -853,8 +771,8 @@ packages:
react: 18.2.0
dev: false
/@mui/system@5.14.3(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.18)(react@18.2.0):
resolution: {integrity: sha512-b+C+j9+75+/iIYSa+1S4eCMc9MDNrj9hzWfExJqS2GffuNocRagjBZFyjtMqsLWLxMxQIX8Cg6j0hAioiw+WfQ==}
/@mui/system@5.14.5(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.18)(react@18.2.0):
resolution: {integrity: sha512-mextXZHDeGcR7E1kx43TRARrVXy+gI4wzpUgNv7MqZs1dvTVXQGVeAT6ydj9d6FUqHBPMNLGV/21vJOrpqsL+w==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
@ -872,10 +790,10 @@ packages:
'@babel/runtime': 7.22.6
'@emotion/react': 11.11.1(@types/react@18.2.18)(react@18.2.0)
'@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.18)(react@18.2.0)
'@mui/private-theming': 5.13.7(@types/react@18.2.18)(react@18.2.0)
'@mui/private-theming': 5.14.5(@types/react@18.2.18)(react@18.2.0)
'@mui/styled-engine': 5.13.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@mui/types': 7.2.4(@types/react@18.2.18)
'@mui/utils': 5.14.3(react@18.2.0)
'@mui/utils': 5.14.5(react@18.2.0)
'@types/react': 18.2.18
clsx: 2.0.0
csstype: 3.1.2
@ -894,8 +812,8 @@ packages:
'@types/react': 18.2.18
dev: false
/@mui/utils@5.14.3(react@18.2.0):
resolution: {integrity: sha512-gZ6Etw+ppO43GYc1HFZSLjwd4DoZoa+RrYTD25wQLfzcSoPjVoC/zZqA2Lkq0zjgwGBQOSxKZI6jfp9uXR+kgw==}
/@mui/utils@5.14.5(react@18.2.0):
resolution: {integrity: sha512-6Hzw63VR9C5xYv+CbjndoRLU6Gntal8rJ5W+GUzkyHrGWIyYPWZPa6AevnyGioySNETATe1H9oXS8f/7qgIHJA==}
engines: {node: '>=12.0.0'}
peerDependencies:
react: ^17.0.0 || ^18.0.0
@ -1066,6 +984,26 @@ packages:
'@swc/core-win32-x64-msvc': 1.3.73
dev: true
/@trivago/prettier-plugin-sort-imports@4.1.0(prettier@2.6.2):
resolution: {integrity: sha512-aTr6QPFaPAAzPRFn9yWB/9yKi3ZAFqfGpxIGLPWuQfYJFGUed+W3KKwxntsoCiNvNE2iuKOg6haMo5KG8WXltg==}
peerDependencies:
'@vue/compiler-sfc': 3.x
prettier: 2.x
peerDependenciesMeta:
'@vue/compiler-sfc':
optional: true
dependencies:
'@babel/generator': 7.17.7
'@babel/parser': 7.22.7
'@babel/traverse': 7.17.3
'@babel/types': 7.17.0
javascript-natural-sort: 0.7.1
lodash: 4.17.21
prettier: 2.6.2
transitivePeerDependencies:
- supports-color
dev: true
/@types/hoist-non-react-statics@3.3.1:
resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==}
dependencies:
@ -1259,13 +1197,13 @@ packages:
eslint-visitor-keys: 3.4.2
dev: true
/@vitejs/plugin-react-swc@3.0.0(vite@4.2.1):
/@vitejs/plugin-react-swc@3.0.0(vite@4.2.3):
resolution: {integrity: sha512-vYlodz/mjYRbxMGbHzDgR8aPR+z8n7K/enWkyBGH096xrL2DIPCuTvQVRYPTXGyy6wO7OFiMxZ3r4nKQD1sH0A==}
peerDependencies:
vite: ^4.0.0
dependencies:
'@swc/core': 1.3.73
vite: 4.2.1(@types/node@20.3.1)
vite: 4.2.3(@types/node@20.3.1)
transitivePeerDependencies:
- '@swc/helpers'
dev: true
@ -1556,6 +1494,7 @@ packages:
/convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
dev: false
/copy-to-clipboard@3.3.2:
resolution: {integrity: sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==}
@ -1785,7 +1724,7 @@ packages:
eslint: 8.46.0
dev: true
/eslint-plugin-prettier@4.0.0(eslint-config-prettier@8.3.0)(eslint@8.46.0)(prettier@2.5.1):
/eslint-plugin-prettier@4.0.0(eslint-config-prettier@8.3.0)(eslint@8.46.0)(prettier@2.6.2):
resolution: {integrity: sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==}
engines: {node: '>=6.0.0'}
peerDependencies:
@ -1798,7 +1737,7 @@ packages:
dependencies:
eslint: 8.46.0
eslint-config-prettier: 8.3.0(eslint@8.46.0)
prettier: 2.5.1
prettier: 2.6.2
prettier-linter-helpers: 1.0.0
dev: true
@ -2041,11 +1980,6 @@ packages:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
dev: true
/gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
dev: true
/get-intrinsic@1.2.1:
resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==}
dependencies:
@ -2375,6 +2309,10 @@ packages:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/javascript-natural-sort@0.7.1:
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
dev: true
/jiti@1.19.1:
resolution: {integrity: sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==}
hasBin: true
@ -2408,12 +2346,6 @@ packages:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
dev: true
/json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
hasBin: true
dev: true
/jsx-ast-utils@3.3.5:
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
engines: {node: '>=4.0'}
@ -2456,18 +2388,16 @@ packages:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
dependencies:
js-tokens: 4.0.0
/lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
dependencies:
yallist: 3.1.1
dev: true
/lru-cache@6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@ -2773,7 +2703,6 @@ packages:
nanoid: 3.3.6
picocolors: 1.0.0
source-map-js: 1.0.2
dev: false
/prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
@ -2787,8 +2716,8 @@ packages:
fast-diff: 1.3.0
dev: true
/prettier@2.5.1:
resolution: {integrity: sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==}
/prettier@2.6.2:
resolution: {integrity: sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: true
@ -3089,7 +3018,6 @@ packages:
/source-map@0.5.7:
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
engines: {node: '>=0.10.0'}
dev: false
/string.prototype.matchall@4.0.8:
resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==}
@ -3346,8 +3274,8 @@ packages:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false
/vite@4.2.1(@types/node@20.3.1):
resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==}
/vite@4.2.3(@types/node@20.3.1):
resolution: {integrity: sha512-kLU+m2q0Y434Y1kCy3TchefAdtFso0ILi0dLyFV8Us3InXTU11H/B5ZTqCKIQHzSKNxVG/yEx813EA9f1imQ9A==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
@ -3373,7 +3301,7 @@ packages:
dependencies:
'@types/node': 20.3.1
esbuild: 0.17.19
postcss: 8.4.21
postcss: 8.4.27
resolve: 1.22.2
rollup: 3.27.0
optionalDependencies:
@ -3417,10 +3345,6 @@ packages:
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
/yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
dev: true
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: true

View File

@ -1,4 +1,5 @@
import { Button, Link, Modal, ModalDialog } from "@mui/joy";
import { useTranslation } from "react-i18next";
import Icon from "./Icon";
interface Props {
@ -7,12 +8,13 @@ interface Props {
const AboutDialog: React.FC<Props> = (props: Props) => {
const { onClose } = props;
const { t } = useTranslation();
return (
<Modal open={true}>
<ModalDialog>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-lg font-medium">About</span>
<span className="text-lg font-medium">{t("common.about")}</span>
<Button variant="plain" onClick={onClose}>
<Icon.X className="w-5 h-auto text-gray-600" />
</Button>

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