37 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
87 changed files with 4063 additions and 582 deletions

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

@ -71,7 +71,7 @@ func JWTMiddleware(s *APIV1Service, next echo.HandlerFunc, secret string) echo.H
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")

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

@ -17,16 +17,6 @@ import (
"google.golang.org/grpc/status"
)
var authenticationAllowlistMethods = map[string]bool{}
// IsAuthenticationAllowed returns whether the method is exempted from authentication.
func IsAuthenticationAllowed(fullMethodName string) bool {
if strings.HasPrefix(fullMethodName, "/grpc.reflection") {
return true
}
return authenticationAllowlistMethods[fullMethodName]
}
// ContextKey is the key type of context value.
type ContextKey int
@ -63,11 +53,23 @@ func (in *GRPCAuthInterceptor) AuthenticationInterceptor(ctx context.Context, re
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 {

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]
}

View File

@ -6,8 +6,10 @@ import (
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 {
@ -75,6 +77,91 @@ func (s *ShortcutService) GetShortcut(ctx context.Context, request *apiv2pb.GetS
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,

View File

@ -9,6 +9,7 @@ import (
"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"
@ -30,6 +31,22 @@ func NewUserService(secret string, store *store.Store) *UserService {
}
}
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,
@ -48,6 +65,43 @@ 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 {

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 {

View File

@ -4,6 +4,12 @@ Slash provides a browser extension to help you use your shortcuts in the search
## 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.
@ -16,14 +22,6 @@ Slash provides a browser extension to help you use your shortcuts in the search
![](./assets/extension-usage/copy-access-token.png)
### Install the extension
> **Note**: The extension is not published to the Chrome Web Store yet. You can install it from the source code.
For Chromuim based browsers, you can download the packed extension from the [resources](https://github.com/boojack/slash/tree/main/extension/resources).
For Firefox, we don't support the Firefox Add-ons platform yet. And we are working on it.
### Configure the extension
1. Click on the extension icon and click on the "Settings" button.

View File

@ -1,9 +1,8 @@
{
"name": "slash-extexnsion",
"name": "slash-extension",
"displayName": "Slash",
"version": "0.1.0",
"version": "0.1.4",
"description": "An open source, self-hosted bookmarks and link sharing platform. Save and share your links very easily.",
"author": "steven",
"scripts": {
"dev": "plasmo dev",
"build": "plasmo build",
@ -12,6 +11,7 @@
"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",
@ -46,14 +46,11 @@
"typescript": "5.1.6"
},
"manifest": {
"host_permissions": [
"http://*/*",
"https://*/*"
],
"omnibox": {
"keyword": "s"
},
"permissions": [
"tabs",
"activeTab",
"scripting",
"storage"
]
}

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.15)(react@18.2.0)
@ -339,6 +342,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:

View File

@ -20,6 +20,15 @@ chrome.tabs.onUpdated.addListener(async (tabId, _, tab) => {
}
});
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) {

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,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

@ -1,17 +1,15 @@
import { Button } from "@mui/joy";
import { IconButton } from "@mui/joy";
import { useStorage } from "@plasmohq/storage/hook";
import axios from "axios";
import { useEffect, useState } from "react";
import { useEffect } from "react";
import { toast } from "react-hot-toast";
import { ListShortcutsResponse } from "@/types/proto/api/v2/shortcut_service_pb";
import "../style.css";
import Icon from "./Icon";
const PullShortcutsButton = () => {
const [domain] = useStorage("domain");
const [accessToken] = useStorage("access_token");
const [, setShortcuts] = useStorage("shortcuts");
const [isPulling, setIsPulling] = useState(false);
useEffect(() => {
if (domain && accessToken) {
@ -21,7 +19,6 @@ const PullShortcutsButton = () => {
const handlePullShortcuts = async (silence = false) => {
try {
setIsPulling(true);
const {
data: { shortcuts },
} = await axios.get<ListShortcutsResponse>(`${domain}/api/v2/shortcuts`, {
@ -36,13 +33,12 @@ const PullShortcutsButton = () => {
} catch (error) {
toast.error("Failed to pull shortcuts, error: " + error.message);
}
setIsPulling(false);
};
return (
<Button loading={isPulling} color="neutral" variant="plain" size="sm" onClick={() => handlePullShortcuts()}>
<IconButton color="neutral" variant="plain" size="sm" onClick={() => handlePullShortcuts()}>
<Icon.RefreshCcw className="w-4 h-auto" />
</Button>
</IconButton>
);
};

View File

@ -1,8 +1,12 @@
import { Button, Input } from "@mui/joy";
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 {
@ -17,6 +21,8 @@ const IndexOptions = () => {
domain,
accessToken,
});
const [shortcuts] = useStorage<Shortcut[]>("shortcuts", []);
const isInitialized = domain && accessToken;
useEffect(() => {
setSettingState({
@ -41,16 +47,41 @@ const IndexOptions = () => {
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 font-mono">
<Icon.CircleSlash className="w-8 h-auto mr-2 text-gray-500" />
<span className="text-lg">Slash</span>
<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 className="text-lg">Setting</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">
<span className="mb-2 text-base">Domain</span>
<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"
@ -79,6 +110,20 @@ const IndexOptions = () => {
<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" />

View File

@ -1,10 +1,12 @@
import { Button } from "@mui/joy";
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 Icon from "./components/Icon";
import PullShortcutsButton from "./components/PullShortcutsButton";
import ShortcutsContainer from "./components/ShortcutsContainer";
import "./style.css";
const IndexPopup = () => {
@ -19,40 +21,69 @@ const IndexPopup = () => {
const handleRefreshButtonClick = () => {
chrome.runtime.reload();
chrome.browserAction.setPopup({ popup: "" });
};
return (
<>
<div className="w-full min-w-[480px] p-4">
<div className="w-full flex flex-row justify-between items-center text-sm">
<div className="flex flex-row justify-start items-center font-mono">
<Icon.CircleSlash className="w-5 h-auto mr-1 text-gray-500 -mt-0.5" />
<span className="font-mono">Slash</span>
<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="mr-1 text-gray-500">({shortcuts.length})</span>
<span className="text-gray-500 mr-0.5">({shortcuts.length})</span>
<PullShortcutsButton />
</>
)}
</div>
<div>
<Button size="sm" variant="plain" color="neutral" onClick={handleSettingButtonClick}>
<Icon.Settings className="w-5 h-auto" />
</Button>
</div>
<div>{isInitialized && <CreateShortcutsButton />}</div>
</div>
<div className="w-full mt-4">
{isInitialized ? (
shortcuts.length !== 0 ? (
<>
{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>

View File

@ -236,3 +236,94 @@ export declare class GetShortcutResponse extends Message<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

@ -90,3 +90,41 @@ export const GetShortcutResponse = proto3.makeMessageType(
],
);
/**
* @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

@ -66,6 +66,11 @@ export declare class User extends Message<User> {
*/
nickname: string;
/**
* @generated from field: string password = 9;
*/
password: string;
constructor(data?: PartialMessage<User>);
static readonly runtime: typeof proto3;
@ -81,6 +86,49 @@ export declare class User extends Message<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
*/
@ -129,6 +177,97 @@ export declare class GetUserResponse extends Message<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
*/

View File

@ -31,6 +31,25 @@ export const User = proto3.makeMessageType(
{ 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 },
],
);
@ -54,6 +73,44 @@ export const GetUserResponse = proto3.makeMessageType(
],
);
/**
* @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
*/

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 */ },
],
);

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

@ -9,6 +9,7 @@ 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"};
}
@ -17,6 +18,18 @@ service ShortcutService {
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 {
@ -76,3 +89,17 @@ message GetShortcutRequest {
message GetShortcutResponse {
Shortcut shortcut = 1;
}
message CreateShortcutRequest {
Shortcut shortcut = 1;
}
message CreateShortcutResponse {
Shortcut shortcut = 1;
}
message DeleteShortcutRequest {
string name = 1;
}
message DeleteShortcutResponse {}

View File

@ -10,11 +10,27 @@ 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"};
@ -49,6 +65,8 @@ message User {
string email = 7;
string nickname = 8;
string password = 9;
}
enum Role {
@ -59,6 +77,12 @@ enum Role {
USER = 2;
}
message ListUsersRequest {}
message ListUsersResponse {
repeated User users = 1;
}
message GetUserRequest {
int32 id = 1;
}
@ -67,6 +91,20 @@ 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;

View File

@ -7,6 +7,10 @@
- [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)
@ -21,12 +25,18 @@
- [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)
@ -74,6 +84,61 @@
<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
@ -199,8 +264,10 @@
| Method Name | Request Type | Response Type | Description |
| ----------- | ------------ | ------------- | ------------|
| ListShortcuts | [ListShortcutsRequest](#slash-api-v2-ListShortcutsRequest) | [ListShortcutsResponse](#slash-api-v2-ListShortcutsResponse) | |
| 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. |
@ -244,6 +311,36 @@
<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
@ -270,6 +367,31 @@
<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
@ -330,6 +452,31 @@
<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
@ -345,6 +492,7 @@
| role | [Role](#slash-api-v2-Role) | | |
| email | [string](#string) | | |
| nickname | [string](#string) | | |
| password | [string](#string) | | |
@ -395,7 +543,10 @@
| Method Name | Request Type | Response Type | Description |
| ----------- | ------------ | ------------- | ------------|
| 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

@ -450,6 +450,185 @@ func (x *GetShortcutResponse) GetShortcut() *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{
@ -506,40 +685,71 @@ var file_api_v2_shortcut_service_proto_rawDesc = []byte{
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63,
0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74,
0x52, 0x08, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x2a, 0x50, 0x0a, 0x0a, 0x56, 0x69,
0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x16, 0x56, 0x49, 0x53, 0x49,
0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x10,
0x01, 0x12, 0x0d, 0x0a, 0x09, 0x57, 0x4f, 0x52, 0x4b, 0x53, 0x50, 0x41, 0x43, 0x45, 0x10, 0x02,
0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x03, 0x32, 0x83, 0x02, 0x0a,
0x0f, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x12, 0x73, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74,
0x73, 0x12, 0x22, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32,
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75,
0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93,
0x02, 0x13, 0x12, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x73, 0x68, 0x6f, 0x72,
0x74, 0x63, 0x75, 0x74, 0x73, 0x12, 0x7b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x68, 0x6f, 0x72,
0x74, 0x63, 0x75, 0x74, 0x12, 0x20, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x61,
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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0xda, 0x41, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76,
0x32, 0x2f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d,
0x65, 0x7d, 0x42, 0xab, 0x01, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x32, 0x42, 0x14, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75,
0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a,
0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x6f, 0x6a,
0x61, 0x63, 0x6b, 0x2f, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
0x67, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69, 0x76, 0x32,
0xa2, 0x02, 0x03, 0x53, 0x41, 0x58, 0xaa, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x41,
0x70, 0x69, 0x2e, 0x56, 0x32, 0xca, 0x02, 0x0c, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70,
0x69, 0x5c, 0x56, 0x32, 0xe2, 0x02, 0x18, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x5c, 0x41, 0x70, 0x69,
0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea,
0x02, 0x0e, 0x53, 0x6c, 0x61, 0x73, 0x68, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x32,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
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 (
@ -555,7 +765,7 @@ func file_api_v2_shortcut_service_proto_rawDescGZIP() []byte {
}
var file_api_v2_shortcut_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_api_v2_shortcut_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_api_v2_shortcut_service_proto_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
@ -564,23 +774,33 @@ var file_api_v2_shortcut_service_proto_goTypes = []interface{}{
(*ListShortcutsResponse)(nil), // 4: slash.api.v2.ListShortcutsResponse
(*GetShortcutRequest)(nil), // 5: slash.api.v2.GetShortcutRequest
(*GetShortcutResponse)(nil), // 6: slash.api.v2.GetShortcutResponse
(RowStatus)(0), // 7: slash.api.v2.RowStatus
(*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{
7, // 0: slash.api.v2.Shortcut.row_status:type_name -> slash.api.v2.RowStatus
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
3, // 5: slash.api.v2.ShortcutService.ListShortcuts:input_type -> slash.api.v2.ListShortcutsRequest
5, // 6: slash.api.v2.ShortcutService.GetShortcut:input_type -> slash.api.v2.GetShortcutRequest
4, // 7: slash.api.v2.ShortcutService.ListShortcuts:output_type -> slash.api.v2.ListShortcutsResponse
6, // 8: slash.api.v2.ShortcutService.GetShortcut:output_type -> slash.api.v2.GetShortcutResponse
7, // [7:9] is the sub-list for method output_type
5, // [5:7] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
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() }
@ -662,6 +882,54 @@ func file_api_v2_shortcut_service_proto_init() {
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{
@ -669,7 +937,7 @@ func file_api_v2_shortcut_service_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_v2_shortcut_service_proto_rawDesc,
NumEnums: 1,
NumMessages: 6,
NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -101,6 +101,92 @@ func local_request_ShortcutService_GetShortcut_0(ctx context.Context, marshaler
}
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.
@ -157,6 +243,56 @@ func RegisterShortcutServiceHandlerServer(ctx context.Context, mux *runtime.Serv
})
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
}
@ -242,6 +378,50 @@ func RegisterShortcutServiceHandlerClient(ctx context.Context, mux *runtime.Serv
})
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
}
@ -249,10 +429,18 @@ 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

@ -21,15 +21,22 @@ 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 {
@ -58,13 +65,36 @@ func (c *shortcutServiceClient) GetShortcut(ctx context.Context, in *GetShortcut
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()
}
@ -78,6 +108,12 @@ func (UnimplementedShortcutServiceServer) ListShortcuts(context.Context, *ListSh
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.
@ -127,6 +163,42 @@ func _ShortcutService_GetShortcut_Handler(srv interface{}, ctx context.Context,
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)
@ -142,6 +214,14 @@ var ShortcutService_ServiceDesc = grpc.ServiceDesc{
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,6 +101,92 @@ 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
@ -281,6 +385,31 @@ func local_request_UserService_DeleteUserAccessToken_0(ctx context.Context, mars
// 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()
@ -306,6 +435,56 @@ 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()
@ -422,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()
@ -444,6 +645,50 @@ 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()
@ -514,8 +759,14 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
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"}, ""))
@ -524,8 +775,14 @@ var (
)
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

View File

@ -19,7 +19,10 @@ 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"
@ -29,8 +32,14 @@ const (
//
// 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.
@ -47,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...)
@ -56,6 +74,24 @@ 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...)
@ -87,8 +123,14 @@ func (c *userServiceClient) DeleteUserAccessToken(ctx context.Context, in *Delet
// 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.
@ -102,9 +144,18 @@ 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")
}
@ -127,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 {
@ -145,6 +214,42 @@ 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 {
@ -206,10 +311,22 @@ 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,

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)
@ -23,6 +26,37 @@
<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>

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,9 @@
syntax = "proto3";
package slash.store;
option go_package = "gen/store";
message ActivityShorcutCreatePayload {
int32 shortcut_id = 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

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.1"
var Version = "0.4.3"
// DevVersion is the service current development version.
var DevVersion = "0.4.1"
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

@ -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

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

View File

@ -11,7 +11,7 @@
"@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",

44
web/pnpm-lock.yaml generated
View File

@ -15,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)
@ -670,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
@ -684,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
@ -694,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
@ -718,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
@ -732,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
@ -743,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
@ -771,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
@ -790,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
@ -812,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

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>

View File

@ -1,30 +0,0 @@
import { Button, Modal, ModalDialog } from "@mui/joy";
import AnalyticsView from "./AnalyticsView";
import Icon from "./Icon";
interface Props {
shortcutId: ShortcutId;
onClose: () => void;
}
const AnalyticsDialog: React.FC<Props> = (props: Props) => {
const { shortcutId, onClose } = props;
return (
<Modal open={true}>
<ModalDialog>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-lg font-medium">Analytics</span>
<Button variant="plain" onClick={onClose}>
<Icon.X className="w-5 h-auto text-gray-600" />
</Button>
</div>
<div className="max-w-full w-80 sm:w-96">
<AnalyticsView className="grid grid-cols-1 gap-2" shortcutId={shortcutId} />
</div>
</ModalDialog>
</Modal>
);
};
export default AnalyticsDialog;

View File

@ -1,5 +1,6 @@
import classNames from "classnames";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import * as api from "../helpers/api";
import Icon from "./Icon";
@ -10,6 +11,7 @@ interface Props {
const AnalyticsView: React.FC<Props> = (props: Props) => {
const { shortcutId, className } = props;
const { t } = useTranslation();
const [analytics, setAnalytics] = useState<AnalysisData | null>(null);
const [selectedDeviceTab, setSelectedDeviceTab] = useState<"os" | "browser">("browser");
@ -24,12 +26,12 @@ const AnalyticsView: React.FC<Props> = (props: Props) => {
{analytics ? (
<>
<div className="w-full">
<p className="w-full h-8 px-2">Top Sources</p>
<p className="w-full h-8 px-2">{t("analytics.top-sources")}</p>
<div className="w-full mt-1 overflow-hidden shadow ring-1 ring-black ring-opacity-5 rounded-lg">
<div className="w-full divide-y divide-gray-300">
<div className="w-full flex flex-row justify-between items-center">
<span className="py-2 px-2 text-left font-semibold text-sm text-gray-500">Source</span>
<span className="py-2 pr-2 text-right font-semibold text-sm text-gray-500">Visitors</span>
<span className="py-2 px-2 text-left font-semibold text-sm text-gray-500">{t("analytics.source")}</span>
<span className="py-2 pr-2 text-right font-semibold text-sm text-gray-500">{t("analytics.visitors")}</span>
</div>
<div className="w-full divide-y divide-gray-200">
{analytics.referenceData.map((reference) => (
@ -53,7 +55,7 @@ const AnalyticsView: React.FC<Props> = (props: Props) => {
<div className="w-full">
<div className="w-full h-8 px-2 flex flex-row justify-between items-center">
<span>Devices</span>
<span>{t("analytics.devices")}</span>
<div>
<button
className={`whitespace-nowrap border-b-2 px-1 text-sm font-medium ${
@ -63,7 +65,7 @@ const AnalyticsView: React.FC<Props> = (props: Props) => {
}`}
onClick={() => setSelectedDeviceTab("browser")}
>
Browser
{t("analytics.browser")}
</button>
<span className="text-gray-200 font-mono mx-1">/</span>
<button
@ -83,8 +85,8 @@ const AnalyticsView: React.FC<Props> = (props: Props) => {
{selectedDeviceTab === "browser" ? (
<div className="w-full divide-y divide-gray-300">
<div className="w-full flex flex-row justify-between items-center">
<span className="py-2 px-2 text-left text-sm font-semibold text-gray-500">Browsers</span>
<span className="py-2 pr-2 text-right text-sm font-semibold text-gray-500">Visitors</span>
<span className="py-2 px-2 text-left text-sm font-semibold text-gray-500">{t("analytics.browsers")}</span>
<span className="py-2 pr-2 text-right text-sm font-semibold text-gray-500">{t("analytics.visitors")}</span>
</div>
<div className="w-full divide-y divide-gray-200">
{analytics.browserData.map((reference) => (
@ -98,8 +100,8 @@ const AnalyticsView: React.FC<Props> = (props: Props) => {
) : (
<div className="w-full divide-y divide-gray-300">
<div className="w-full flex flex-row justify-between items-center">
<span className="py-2 px-2 text-left text-sm font-semibold text-gray-500">Operating system</span>
<span className="py-2 pr-2 text-right text-sm font-semibold text-gray-500">Visitors</span>
<span className="py-2 px-2 text-left text-sm font-semibold text-gray-500">{t("analytics.operating-system")}</span>
<span className="py-2 pr-2 text-right text-sm font-semibold text-gray-500">{t("analytics.visitors")}</span>
</div>
<div className="w-full divide-y divide-gray-200">
{analytics.deviceData.map((device) => (
@ -117,7 +119,7 @@ const AnalyticsView: React.FC<Props> = (props: Props) => {
) : (
<div className="py-12 w-full flex flex-row justify-center items-center opacity-80">
<Icon.Loader className="mr-2 w-5 h-auto animate-spin" />
loading
{t("common.loading")}
</div>
)}
</div>

View File

@ -1,6 +1,7 @@
import { Button, Input, Modal, ModalDialog } from "@mui/joy";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import useLoading from "../hooks/useLoading";
import useUserStore from "../stores/v1/user";
import Icon from "./Icon";
@ -11,6 +12,7 @@ interface Props {
const ChangePasswordDialog: React.FC<Props> = (props: Props) => {
const { onClose } = props;
const { t } = useTranslation();
const userStore = useUserStore();
const [newPassword, setNewPassword] = useState("");
const [newPasswordAgain, setNewPasswordAgain] = useState("");
@ -77,10 +79,10 @@ const ChangePasswordDialog: React.FC<Props> = (props: Props) => {
</div>
<div className="w-full flex flex-row justify-end items-center space-x-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={handleCloseBtnClick}>
Cancel
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={handleSaveBtnClick}>
Save
{t("common.save")}
</Button>
</div>
</div>

View File

@ -2,6 +2,7 @@ import { Button, Input, Modal, ModalDialog, Radio, RadioGroup } from "@mui/joy";
import axios from "axios";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import useLoading from "../hooks/useLoading";
import useUserStore from "../stores/v1/user";
import Icon from "./Icon";
@ -33,6 +34,7 @@ interface State {
const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
const { onClose, onConfirm } = props;
const { t } = useTranslation();
const currentUser = useUserStore().getCurrentUser();
const [state, setState] = useState({
description: "",
@ -119,10 +121,10 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
</div>
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
<Button color="neutral" variant="plain" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={onClose}>
Cancel
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={handleSaveBtnClick}>
Create
{t("common.create")}
</Button>
</div>
</div>

View File

@ -164,13 +164,13 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
title: state.shortcutCreate.title,
description: state.shortcutCreate.description,
visibility: state.shortcutCreate.visibility,
tags: tag.split(" "),
tags: tag.split(" ").filter(Boolean),
openGraphMetadata: state.shortcutCreate.openGraphMetadata,
});
} else {
await shortcutService.createShortcut({
...state.shortcutCreate,
tags: tag.split(" "),
tags: tag.split(" ").filter(Boolean),
});
}
@ -331,10 +331,10 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
<Button color="neutral" variant="plain" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={onClose}>
Cancel
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={handleSaveBtnClick}>
Save
{t("common.save")}
</Button>
</div>
</div>

View File

@ -2,6 +2,7 @@ import { Button, Input, Modal, ModalDialog, Radio, RadioGroup } from "@mui/joy";
import { isUndefined } from "lodash-es";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import useLoading from "../hooks/useLoading";
import useUserStore from "../stores/v1/user";
import Icon from "./Icon";
@ -20,6 +21,7 @@ const roles: Role[] = ["USER", "ADMIN"];
const CreateUserDialog: React.FC<Props> = (props: Props) => {
const { onClose, onConfirm, user } = props;
const { t } = useTranslation();
const userStore = useUserStore();
const [state, setState] = useState<State>({
userCreate: {
@ -185,10 +187,10 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => {
</div>
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
<Button color="neutral" variant="plain" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={onClose}>
Cancel
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={handleSaveBtnClick}>
Save
{t("common.save")}
</Button>
</div>
</div>

View File

@ -14,7 +14,7 @@ const DemoBanner: React.FC = () => {
return (
<div className="z-10 relative flex flex-row items-center justify-center w-full py-2 text-sm sm:text-lg font-medium dark:text-gray-300 bg-white dark:bg-zinc-700 shadow">
<div className="w-full max-w-6xl px-4 md:px-12 flex flex-row justify-between items-center gap-x-3">
<span>Slash - An open source, self-hosted bookmarks and link sharing platform</span>
<span>🔗 Slash - An open source, self-hosted bookmarks and link sharing platform</span>
<a
className="shadow flex flex-row justify-center items-center px-2 py-1 rounded-md text-sm sm:text-base text-white bg-blue-600 hover:bg-blue-700"
href="https://github.com/boojack/slash#deploy-with-docker-in-seconds"

View File

@ -1,6 +1,7 @@
import { Button, Input, Modal, ModalDialog } from "@mui/joy";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import useLoading from "../hooks/useLoading";
import useUserStore from "../stores/v1/user";
import Icon from "./Icon";
@ -11,6 +12,7 @@ interface Props {
const EditUserinfoDialog: React.FC<Props> = (props: Props) => {
const { onClose } = props;
const { t } = useTranslation();
const userStore = useUserStore();
const currentUser = userStore.getCurrentUser();
const [email, setEmail] = useState(currentUser.email);
@ -73,10 +75,10 @@ const EditUserinfoDialog: React.FC<Props> = (props: Props) => {
</div>
<div className="w-full flex flex-row justify-end items-center space-x-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={handleCloseBtnClick}>
Cancel
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} loading={requestState.isLoading} onClick={handleSaveBtnClick}>
Save
{t("common.save")}
</Button>
</div>
</div>

View File

@ -2,6 +2,7 @@ import { Button, Modal, ModalDialog } from "@mui/joy";
import { QRCodeCanvas } from "qrcode.react";
import { useRef } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { absolutifyLink } from "../helpers/utils";
import Icon from "./Icon";
@ -12,6 +13,7 @@ interface Props {
const GenerateQRCodeDialog: React.FC<Props> = (props: Props) => {
const { shortcut, onClose } = props;
const { t } = useTranslation();
const containerRef = useRef<HTMLDivElement | null>(null);
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
@ -49,7 +51,7 @@ const GenerateQRCodeDialog: React.FC<Props> = (props: Props) => {
<div className="w-full flex flex-row justify-center items-center px-4">
<Button className="w-full" color="neutral" onClick={handleDownloadQRCodeClick}>
<Icon.Download className="w-4 h-auto mr-1" />
Download
{t("common.download")}
</Button>
</div>
</div>

View File

@ -21,8 +21,8 @@ const Header: React.FC = () => {
<div className="w-full bg-gray-50 border-b border-b-gray-200">
<div className="w-full max-w-6xl mx-auto px-3 md:px-12 py-5 flex flex-row justify-between items-center">
<div className="flex flex-row justify-start items-center shrink mr-2">
<Link to="/" className="text-base font-mono font-medium cursor-pointer flex flex-row justify-start items-center">
<img src="/logo.png" className="w-8 h-auto mr-2" alt="" />
<Link to="/" className="text-lg cursor-pointer flex flex-row justify-start items-center">
<img src="/logo.png" className="w-8 h-auto mr-2 -mt-0.5" alt="" />
Slash
</Link>
</div>

View File

@ -1,8 +1,9 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { shortcutService } from "../services";
import useUserStore from "../stores/v1/user";
import { showCommonDialog } from "./Alert";
import AnalyticsDialog from "./AnalyticsDialog";
import CreateShortcutDialog from "./CreateShortcutDialog";
import GenerateQRCodeDialog from "./GenerateQRCodeDialog";
import Icon from "./Icon";
@ -14,10 +15,11 @@ interface Props {
const ShortcutActionsDropdown = (props: Props) => {
const { shortcut } = props;
const { t } = useTranslation();
const navigate = useNavigate();
const currentUser = useUserStore().getCurrentUser();
const [showEditDialog, setShowEditDialog] = useState<boolean>(false);
const [showQRCodeDialog, setShowQRCodeDialog] = useState<boolean>(false);
const [showAnalyticsDialog, setShowAnalyticsDialog] = useState<boolean>(false);
const havePermission = currentUser.role === "ADMIN" || shortcut.creatorId === currentUser.id;
const handleDeleteShortcutButtonClick = (shortcut: Shortcut) => {
@ -31,6 +33,10 @@ const ShortcutActionsDropdown = (props: Props) => {
});
};
const gotoAnalytics = () => {
navigate(`/shortcut/${shortcut.id}#analytics`);
};
return (
<>
<Dropdown
@ -42,7 +48,7 @@ const ShortcutActionsDropdown = (props: Props) => {
className="w-full px-2 flex flex-row justify-start items-center text-left leading-8 cursor-pointer rounded hover:bg-gray-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
onClick={() => setShowEditDialog(true)}
>
<Icon.Edit className="w-4 h-auto mr-2" /> Edit
<Icon.Edit className="w-4 h-auto mr-2" /> {t("common.edit")}
</button>
)}
<button
@ -53,9 +59,9 @@ const ShortcutActionsDropdown = (props: Props) => {
</button>
<button
className="w-full px-2 flex flex-row justify-start items-center text-left leading-8 cursor-pointer rounded hover:bg-gray-100 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
onClick={() => setShowAnalyticsDialog(true)}
onClick={gotoAnalytics}
>
<Icon.BarChart2 className="w-4 h-auto mr-2" /> Analytics
<Icon.BarChart2 className="w-4 h-auto mr-2" /> {t("analytics.self")}
</button>
{havePermission && (
<button
@ -64,7 +70,7 @@ const ShortcutActionsDropdown = (props: Props) => {
handleDeleteShortcutButtonClick(shortcut);
}}
>
<Icon.Trash className="w-4 h-auto mr-2" /> Delete
<Icon.Trash className="w-4 h-auto mr-2" /> {t("common.delete")}
</button>
)}
</>
@ -80,8 +86,6 @@ const ShortcutActionsDropdown = (props: Props) => {
)}
{showQRCodeDialog && <GenerateQRCodeDialog shortcut={shortcut} onClose={() => setShowQRCodeDialog(false)} />}
{showAnalyticsDialog && <AnalyticsDialog shortcutId={shortcut.id} onClose={() => setShowAnalyticsDialog(false)} />}
</>
);
};

View File

@ -8,7 +8,6 @@ import { Link } from "react-router-dom";
import { absolutifyLink } from "../helpers/utils";
import useFaviconStore from "../stores/v1/favicon";
import useViewStore from "../stores/v1/view";
import AnalyticsDialog from "./AnalyticsDialog";
import Icon from "./Icon";
import ShortcutActionsDropdown from "./ShortcutActionsDropdown";
import VisibilityIcon from "./VisibilityIcon";
@ -23,7 +22,6 @@ const ShortcutView = (props: Props) => {
const viewStore = useViewStore();
const faviconStore = useFaviconStore();
const [favicon, setFavicon] = useState<string | undefined>(undefined);
const [showAnalyticsDialog, setShowAnalyticsDialog] = useState<boolean>(false);
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
useEffect(() => {
@ -55,7 +53,7 @@ const ShortcutView = (props: Props) => {
<div className="w-full flex flex-row justify-start items-center">
<a
className={classNames(
"max-w-[calc(100%-24px) flex flex-row px-1 mr-1 justify-start items-center cursor-pointer rounded-md hover:bg-gray-100 hover:shadow"
"max-w-[calc(100%-36px)] flex flex-row px-1 mr-1 justify-start items-center cursor-pointer rounded-md hover:bg-gray-100 hover:shadow"
)}
target="_blank"
href={shortcutLink}
@ -124,18 +122,16 @@ const ShortcutView = (props: Props) => {
</div>
</Tooltip>
<Tooltip title="View count" variant="solid" placement="top" arrow>
<div
<Link
to={`/shortcut/${shortcut.id}#analytics`}
className="w-auto px-2 leading-6 flex flex-row justify-start items-center border rounded-full cursor-pointer text-gray-500 text-sm"
onClick={() => setShowAnalyticsDialog(true)}
>
<Icon.BarChart2 className="w-4 h-auto mr-1" />
{shortcut.view} visits
</div>
</Link>
</Tooltip>
</div>
</div>
{showAnalyticsDialog && <AnalyticsDialog shortcutId={shortcut.id} onClose={() => setShowAnalyticsDialog(false)} />}
</>
);
};

View File

@ -1,5 +1,4 @@
import { Button, Divider, Option, Select } from "@mui/joy";
import { toast } from "react-hot-toast";
import { Divider, Option, Select, Switch } from "@mui/joy";
import useViewStore from "../stores/v1/view";
import Icon from "./Icon";
import Dropdown from "./common/Dropdown";
@ -10,11 +9,6 @@ const ViewSetting = () => {
const { field, direction } = order;
const displayStyle = viewStore.displayStyle || "full";
const handleReset = () => {
viewStore.setOrder({ field: "name", direction: "asc" });
toast.success("Order reset");
};
return (
<Dropdown
trigger={
@ -24,13 +18,16 @@ const ViewSetting = () => {
}
actionsClassName="!mt-3 !-right-2"
actions={
<div className="w-52 p-2 pt-0 gap-2 flex flex-col justify-start items-start" onClick={(e) => e.stopPropagation()}>
<div className="w-full flex flex-row justify-between items-center mt-1">
<span className="text-sm font-medium">View order</span>
<Button size="sm" variant="plain" color="neutral" onClick={handleReset}>
<Icon.RefreshCw className="w-4 h-auto text-gray-500" />
</Button>
<div className="w-52 p-2 gap-2 flex flex-col justify-start items-start" onClick={(e) => e.stopPropagation()}>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-2">Compact mode</span>
<Switch
size="sm"
checked={displayStyle === "compact"}
onChange={(event) => viewStore.setDisplayStyle(event.target.checked ? "compact" : "full")}
/>
</div>
<Divider className="!my-1" />
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-2">Order by</span>
<Select size="sm" value={field} onChange={(_, value) => viewStore.setOrder({ field: value as any })}>
@ -47,14 +44,6 @@ const ViewSetting = () => {
<Option value={"desc"}>DESC</Option>
</Select>
</div>
<Divider />
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-2">Display</span>
<Select size="sm" value={displayStyle} onChange={(_, value) => viewStore.setDisplayStyle(value as any)}>
<Option value={"full"}>Full</Option>
<Option value={"compact"}>Compact</Option>
</Select>
</div>
</div>
}
></Dropdown>

View File

@ -1,4 +1,4 @@
import { Button } from "@mui/joy";
import { Button, IconButton } from "@mui/joy";
import axios from "axios";
import copy from "copy-to-clipboard";
import { useEffect, useState } from "react";
@ -110,7 +110,7 @@ const AccessTokenSection = () => {
{String(userAccessToken.expiresAt ?? "Never")}
</td>
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm">
<Button
<IconButton
color="danger"
variant="plain"
size="sm"
@ -119,7 +119,7 @@ const AccessTokenSection = () => {
}}
>
<Icon.Trash className="w-4 h-auto" />
</Button>
</IconButton>
</td>
</tr>
))}

View File

@ -19,7 +19,7 @@ const AccountSection: React.FC = () => {
{isAdmin && <span className="ml-2 bg-blue-600 text-white px-2 leading-6 text-sm rounded-full">Admin</span>}
</p>
<p className="flex flex-row justify-start items-center">
<span className="mr-3 text-gray-500 font-mono">Email: </span>
<span className="mr-3 text-gray-500">Email: </span>
{currentUser.email}
</p>
<div className="flex flex-row justify-start items-center gap-2 mt-2">

View File

@ -1,7 +1,10 @@
import { Button } from "@mui/joy";
import { Button, IconButton } from "@mui/joy";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import useUserStore from "../../stores/v1/user";
import { showCommonDialog } from "../Alert";
import CreateUserDialog from "../CreateUserDialog";
import Icon from "../Icon";
const MemberSection = () => {
const userStore = useUserStore();
@ -18,6 +21,22 @@ const MemberSection = () => {
setCurrentEditingUser(undefined);
};
const handleDeleteUser = async (user: User) => {
showCommonDialog({
title: "Delete User",
content: `Are you sure to delete user \`${user.nickname}\`? You cannot undo this action.`,
style: "danger",
onConfirm: async () => {
try {
await userStore.deleteUser(user.id);
toast.success(`User \`${user.nickname}\` deleted successfully`);
} catch (error: any) {
toast.error(`Failed to delete user \`${user.nickname}\`: ${error.response.data.message}`);
}
},
});
};
return (
<>
<div className="w-full flex flex-col justify-start items-start space-y-4">
@ -68,16 +87,20 @@ const MemberSection = () => {
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm text-gray-900">{user.nickname}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{user.email}</td>
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">{user.role}</td>
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium">
<button
className="text-indigo-600 hover:text-indigo-900"
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm">
<IconButton
size="sm"
variant="plain"
onClick={() => {
setCurrentEditingUser(user);
setShowCreateUserDialog(true);
}}
>
Edit
</button>
<Icon.PenBox className="w-4 h-auto" />
</IconButton>
<IconButton size="sm" color="danger" variant="plain" onClick={() => handleDeleteUser(user)}>
<Icon.Trash className="w-4 h-auto" />
</IconButton>
</td>
</tr>
))}

View File

@ -43,8 +43,8 @@ export function patchUser(userPatch: UserPatch) {
return axios.patch<User>(`/api/v1/user/${userPatch.id}`, userPatch);
}
export function deleteUser(userDelete: UserDelete) {
return axios.delete(`/api/v1/user/${userDelete.id}`);
export function deleteUser(userId: UserId) {
return axios.delete(`/api/v2/users/${userId}`);
}
export function getShortcutList(shortcutFind?: ShortcutFind) {

View File

@ -1,4 +1,24 @@
{
"common": {
"about": "About",
"loading": "Loading",
"cancel": "Cancel",
"save": "Save",
"create": "Create",
"download": "Download",
"edit": "Edit",
"delete": "Delete"
},
"analytics": {
"self": "Analytics",
"top-sources": "Top Sources",
"source": "Source",
"visitors": "Visitors",
"devices": "Devices",
"browser": "Browser",
"browsers": "Browsers",
"operating-system": "Operating System"
},
"shortcut": {
"visibility": {
"private": {

View File

@ -1,37 +0,0 @@
import { Button } from "@mui/joy";
import { useState } from "react";
import ChangePasswordDialog from "../components/ChangePasswordDialog";
import EditUserinfoDialog from "../components/EditUserinfoDialog";
import useUserStore from "../stores/v1/user";
const Account: React.FC = () => {
const currentUser = useUserStore().getCurrentUser();
const [showEditUserinfoDialog, setShowEditUserinfoDialog] = useState<boolean>(false);
const [showChangePasswordDialog, setShowChangePasswordDialog] = useState<boolean>(false);
return (
<>
<div className="mx-auto max-w-6xl w-full px-3 md:px-12 py-6 flex flex-col justify-start items-start space-y-4">
<p className="text-3xl my-2">{currentUser.nickname}</p>
<p className="leading-8 flex flex-row justify-start items-center">
<span className="mr-3 text-gray-500 font-mono">Email: </span>
{currentUser.email}
</p>
<div className="flex flex-row justify-start items-center gap-2">
<Button variant="outlined" color="neutral" onClick={() => setShowEditUserinfoDialog(true)}>
Edit
</Button>
<Button variant="outlined" color="neutral" onClick={() => setShowChangePasswordDialog(true)}>
Change password
</Button>
</div>
</div>
{showEditUserinfoDialog && <EditUserinfoDialog onClose={() => setShowEditUserinfoDialog(false)} />}
{showChangePasswordDialog && <ChangePasswordDialog onClose={() => setShowChangePasswordDialog(false)} />}
</>
);
};
export default Account;

View File

@ -175,7 +175,7 @@ const ShortcutDetail = () => {
</div>
<div className="w-full flex flex-col mt-8">
<h3 className="pl-1 font-medium text-lg flex flex-row justify-start items-center">
<h3 id="analytics" className="pl-1 font-medium text-lg flex flex-row justify-start items-center">
<Icon.BarChart2 className="w-6 h-auto mr-1" />
Analytics
</h3>

View File

@ -74,7 +74,7 @@ const SignIn: React.FC = () => {
<div className="w-full py-4 grow flex flex-col justify-center items-center">
<div className="flex flex-row justify-start items-center w-auto mx-auto gap-y-2 mb-4">
<img src="/logo.png" className="w-12 h-auto mr-2 -mt-1" alt="logo" />
<span className="text-3xl font-medium font-mono opacity-80">Slash</span>
<span className="text-3xl opacity-80">Slash</span>
</div>
<form className="w-full mt-6" onSubmit={handleSigninBtnClick}>
<div className={`flex flex-col justify-start items-start w-full ${actionBtnLoadingState.isLoading ? "opacity-80" : ""}`}>

View File

@ -78,7 +78,7 @@ const SignUp: React.FC = () => {
<div className="w-full py-4 grow flex flex-col justify-center items-center">
<div className="flex flex-row justify-start items-center w-auto mx-auto gap-y-2 mb-4">
<img src="/logo.png" className="w-12 h-auto mr-2 -mt-1" alt="logo" />
<span className="text-3xl font-medium font-mono opacity-80">Slash</span>
<span className="text-3xl opacity-80">Slash</span>
</div>
<p className="w-full text-2xl mt-6">Create your account</p>
<form className="w-full mt-4" onSubmit={handleSignupBtnClick}>

View File

@ -19,6 +19,7 @@ interface UserState {
getCurrentUser: () => User;
createUser: (userCreate: UserCreate) => Promise<User>;
patchUser: (userPatch: UserPatch) => Promise<void>;
deleteUser: (id: UserId) => Promise<void>;
}
const useUserStore = create<UserState>()((set, get) => ({
@ -67,6 +68,12 @@ const useUserStore = create<UserState>()((set, get) => ({
userMap[user.id] = user;
set(userMap);
},
deleteUser: async (userId: UserId) => {
await api.deleteUser(userId);
const userMap = get().userMapById;
delete userMap[userId];
set(userMap);
},
getUserById: (id: UserId) => {
const userMap = get().userMapById;
return userMap[id] as User;

View File

@ -236,3 +236,94 @@ export declare class GetShortcutResponse extends Message<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

@ -90,3 +90,41 @@ export const GetShortcutResponse = proto3.makeMessageType(
],
);
/**
* @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

@ -66,6 +66,11 @@ export declare class User extends Message<User> {
*/
nickname: string;
/**
* @generated from field: string password = 9;
*/
password: string;
constructor(data?: PartialMessage<User>);
static readonly runtime: typeof proto3;
@ -81,6 +86,49 @@ export declare class User extends Message<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
*/
@ -129,6 +177,97 @@ export declare class GetUserResponse extends Message<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
*/

View File

@ -31,6 +31,25 @@ export const User = proto3.makeMessageType(
{ 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 },
],
);
@ -54,6 +73,44 @@ export const GetUserResponse = proto3.makeMessageType(
],
);
/**
* @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
*/

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 */ },
],
);