server: implement messages and gRPC support for creating messages
This commit is contained in:
@@ -35,6 +35,13 @@ func Start() {
|
||||
r.Route("/{channelID}", func(r chi.Router) {
|
||||
r.Get("/", GetChannel)
|
||||
r.Delete("/", DeleteChannel)
|
||||
|
||||
r.Route("/messages", func(r chi.Router) {
|
||||
r.Get("/", ListMessages)
|
||||
r.Route("/{messageID}", func(r chi.Router) {
|
||||
r.Get("/", GetMessage)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"git.dubyatp.xyz/dubyatp/scannerbot/server/db"
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -14,6 +15,7 @@ var ErrUserNotFound = errors.New("db: user not found")
|
||||
var ErrSessionNotFound = errors.New("db: session not found")
|
||||
var ErrChannelNotFound = errors.New("db: channel not found")
|
||||
var ErrFileNotFound = errors.New("db: file not found")
|
||||
var ErrMessageNotFound = errors.New("db: message not found")
|
||||
|
||||
func dbGetUser(id string) (*User, error) {
|
||||
query := `SELECT id, name, password FROM users WHERE id = $1`
|
||||
@@ -233,3 +235,112 @@ func dbGetFile(id string) (*File, error) {
|
||||
slog.Debug("db: file found", "fileid", file.ID, "filename", file.Name)
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
func DBGetChannel(id string) (*Channel, error) { return dbGetChannel(id) }
|
||||
func DBAddFile(file *File) error { return dbAddFile(file) }
|
||||
|
||||
func DBAddMessage(msg *Message) error {
|
||||
query := `INSERT INTO messages (id, channel, created, content, audio) VALUES ($1, $2, $3, $4, $5)`
|
||||
_, err := db.Pool.Exec(context.Background(), query, msg.ID, msg.Channel.ID, msg.Created, msg.Content, msg.Audio.ID)
|
||||
if err != nil {
|
||||
slog.Error("db: failed to add message", "error", err, "messageid", msg.ID)
|
||||
return fmt.Errorf("failed to add message")
|
||||
}
|
||||
slog.Debug("db: message added", "messageid", msg.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func dbGetMessage(id string) (*Message, error) {
|
||||
query := `SELECT id, channel, created, content, audio FROM messages WHERE id = $1`
|
||||
var channelID, audioID string
|
||||
var msg Message
|
||||
err := db.Pool.QueryRow(context.Background(), query, id).Scan(&msg.ID, &channelID, &msg.Created, &msg.Content, &audioID)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
slog.Debug("db: message not found", "messageid", id)
|
||||
return nil, ErrMessageNotFound
|
||||
} else if err != nil {
|
||||
slog.Error("db: failed to query message", "error", err)
|
||||
return nil, fmt.Errorf("failed to query message")
|
||||
}
|
||||
|
||||
channel, err := dbGetChannel(channelID)
|
||||
if err != nil {
|
||||
slog.Error("db: failed to fetch channel for message", "messageid", id, "channelid", channelID, "error", err)
|
||||
return nil, fmt.Errorf("failed to fetch channel for message")
|
||||
}
|
||||
audio, err := dbGetFile(audioID)
|
||||
if err != nil {
|
||||
slog.Error("db: failed to fetch audio for message", "messageid", id, "audioid", audioID, "error", err)
|
||||
return nil, fmt.Errorf("failed to fetch audio for message")
|
||||
}
|
||||
|
||||
msg.Channel = *channel
|
||||
msg.Audio = *audio
|
||||
slog.Debug("db: message found", "messageid", msg.ID)
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
func dbGetMessagesByChannel(channelID string, from, to *time.Time) ([]*Message, error) {
|
||||
query := `SELECT id, channel, created, content, audio FROM messages WHERE channel = $1`
|
||||
args := []any{channelID}
|
||||
if from != nil {
|
||||
args = append(args, *from)
|
||||
query += fmt.Sprintf(" AND created >= $%d", len(args))
|
||||
}
|
||||
if to != nil {
|
||||
args = append(args, *to)
|
||||
query += fmt.Sprintf(" AND created <= $%d", len(args))
|
||||
}
|
||||
query += " ORDER BY created DESC"
|
||||
rows, err := db.Pool.Query(context.Background(), query, args...)
|
||||
if err != nil {
|
||||
slog.Error("db: failed to query messages", "error", err)
|
||||
return nil, fmt.Errorf("failed to query messages")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type messageRow struct {
|
||||
msg Message
|
||||
channelID string
|
||||
audioID string
|
||||
}
|
||||
|
||||
var rows_ []messageRow
|
||||
for rows.Next() {
|
||||
var mr messageRow
|
||||
if err := rows.Scan(&mr.msg.ID, &mr.channelID, &mr.msg.Created, &mr.msg.Content, &mr.audioID); err != nil {
|
||||
slog.Error("db: failed to scan message", "error", err)
|
||||
return nil, fmt.Errorf("failed to scan message")
|
||||
}
|
||||
rows_ = append(rows_, mr)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
slog.Error("db: row iteration error", "error", err)
|
||||
return nil, fmt.Errorf("failed to iterate messages")
|
||||
}
|
||||
if len(rows_) == 0 {
|
||||
slog.Debug("db: no messages found", "channelid", channelID)
|
||||
return nil, ErrMessageNotFound
|
||||
}
|
||||
|
||||
channel, err := dbGetChannel(channelID)
|
||||
if err != nil {
|
||||
slog.Error("db: failed to fetch channel for messages", "channelid", channelID, "error", err)
|
||||
return nil, fmt.Errorf("failed to fetch channel for messages")
|
||||
}
|
||||
|
||||
var messages []*Message
|
||||
for _, mr := range rows_ {
|
||||
audio, err := dbGetFile(mr.audioID)
|
||||
if err != nil {
|
||||
slog.Error("db: failed to fetch audio for message", "messageid", mr.msg.ID, "audioid", mr.audioID, "error", err)
|
||||
return nil, fmt.Errorf("failed to fetch audio for message")
|
||||
}
|
||||
mr.msg.Channel = *channel
|
||||
mr.msg.Audio = *audio
|
||||
messages = append(messages, &mr.msg)
|
||||
}
|
||||
|
||||
slog.Debug("db: message list returned", "channelid", channelID, "count", len(messages))
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
ID uuid.UUID
|
||||
Channel Channel
|
||||
Created time.Time
|
||||
Content string
|
||||
Audio File
|
||||
}
|
||||
|
||||
type MessagePayload struct {
|
||||
*Message
|
||||
}
|
||||
|
||||
func GetMessage(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Debug("message: entering GetMessage handler")
|
||||
|
||||
messageID := chi.URLParam(r, "messageID")
|
||||
parsed, err := uuid.Parse(messageID)
|
||||
if err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := dbGetMessage(parsed.String())
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrMessageNotFound) {
|
||||
render.Render(w, r, ErrNotFound)
|
||||
} else {
|
||||
slog.Error("message: failed to fetch message", "messageid", parsed.String(), "error", err)
|
||||
render.Render(w, r, ErrInternal(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
slog.Debug("message: rendering message", "messageid", msg.ID)
|
||||
if err := render.Render(w, r, NewMessagePayloadResponse(msg)); err != nil {
|
||||
slog.Error("message: failed to render message", "messageid", parsed.String(), "error", err)
|
||||
render.Render(w, r, ErrInternal(err))
|
||||
}
|
||||
}
|
||||
|
||||
func ListMessages(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Debug("message: entering ListMessages handler")
|
||||
|
||||
channelID := chi.URLParam(r, "channelID")
|
||||
parsed, err := uuid.Parse(channelID)
|
||||
if err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
var from, to *time.Time
|
||||
if v := r.URL.Query().Get("from"); v != "" {
|
||||
t, err := time.Parse(time.RFC3339, v)
|
||||
if err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(fmt.Errorf("invalid 'from' timestamp: %w", err)))
|
||||
return
|
||||
}
|
||||
from = &t
|
||||
}
|
||||
if v := r.URL.Query().Get("to"); v != "" {
|
||||
t, err := time.Parse(time.RFC3339, v)
|
||||
if err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(fmt.Errorf("invalid 'to' timestamp: %w", err)))
|
||||
return
|
||||
}
|
||||
to = &t
|
||||
}
|
||||
|
||||
messages, err := dbGetMessagesByChannel(parsed.String(), from, to)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrMessageNotFound) {
|
||||
render.Render(w, r, ErrNotFound)
|
||||
} else {
|
||||
slog.Error("message: failed to fetch messages", "channelid", parsed.String(), "error", err)
|
||||
render.Render(w, r, ErrInternal(err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
slog.Debug("message: successfully fetched messages", "channelid", parsed.String(), "count", len(messages))
|
||||
if err := render.RenderList(w, r, NewMessageListResponse(messages)); err != nil {
|
||||
slog.Error("message: failed to render message list", "channelid", parsed.String(), "error", err)
|
||||
render.Render(w, r, ErrInternal(err))
|
||||
}
|
||||
}
|
||||
@@ -49,3 +49,19 @@ func NewFilePayloadResponse(file *File) *FilePayload {
|
||||
func (f *FilePayload) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMessagePayloadResponse(msg *Message) *MessagePayload {
|
||||
return &MessagePayload{Message: msg}
|
||||
}
|
||||
|
||||
func NewMessageListResponse(messages []*Message) []render.Renderer {
|
||||
list := []render.Renderer{}
|
||||
for _, msg := range messages {
|
||||
list = append(list, NewMessagePayloadResponse(msg))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (m *MessagePayload) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ require (
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/grpc v1.81.1 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
@@ -30,10 +30,20 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"git.dubyatp.xyz/dubyatp/scannerbot/server/api"
|
||||
pb "git.dubyatp.xyz/dubyatp/scannerbot/server/proto"
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type MessageServer struct {
|
||||
pb.UnimplementedMessageServiceServer
|
||||
}
|
||||
|
||||
func (s *MessageServer) SendMessage(ctx context.Context, req *pb.SendMessageRequest) (*pb.SendMessageResponse, error) {
|
||||
slog.Debug("grpc: entering SendMessage handler")
|
||||
|
||||
channelID, err := uuid.Parse(req.ChannelId)
|
||||
if err != nil {
|
||||
slog.Error("grpc: invalid channel_id", "error", err)
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid channel_id: %v", err)
|
||||
}
|
||||
|
||||
channel, err := api.DBGetChannel(channelID.String())
|
||||
if err != nil {
|
||||
slog.Error("grpc: channel not found", "channelid", channelID, "error", err)
|
||||
return nil, status.Errorf(codes.NotFound, "channel not found")
|
||||
}
|
||||
|
||||
filename := req.AudioFilename
|
||||
if filename == "" {
|
||||
filename = channelID.String() + ".wav"
|
||||
}
|
||||
|
||||
audio, err := api.Store.Save(filename, bytes.NewReader(req.Audio))
|
||||
if err != nil {
|
||||
slog.Error("grpc: failed to save audio file", "error", err)
|
||||
return nil, status.Errorf(codes.Internal, "failed to save audio file")
|
||||
}
|
||||
|
||||
if err := api.DBAddFile(audio); err != nil {
|
||||
slog.Error("grpc: failed to persist audio file record", "error", err)
|
||||
return nil, status.Errorf(codes.Internal, "failed to persist audio file")
|
||||
}
|
||||
|
||||
msg := &api.Message{
|
||||
ID: uuid.New(),
|
||||
Channel: *channel,
|
||||
Created: time.Now(),
|
||||
Content: req.Content,
|
||||
Audio: *audio,
|
||||
}
|
||||
|
||||
if err := api.DBAddMessage(msg); err != nil {
|
||||
slog.Error("grpc: failed to persist message", "error", err)
|
||||
return nil, status.Errorf(codes.Internal, "failed to persist message")
|
||||
}
|
||||
|
||||
slog.Debug("grpc: message saved", "messageid", msg.ID, "channelid", channelID)
|
||||
return &pb.SendMessageResponse{
|
||||
Id: msg.ID.String(),
|
||||
ChannelId: channel.ID.String(),
|
||||
Created: msg.Created.Format(time.RFC3339),
|
||||
Content: msg.Content,
|
||||
AudioId: audio.ID.String(),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net"
|
||||
|
||||
pb "git.dubyatp.xyz/dubyatp/scannerbot/server/proto"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func Start() {
|
||||
lis, err := net.Listen("tcp", ":3001")
|
||||
if err != nil {
|
||||
slog.Error("grpc: failed to listen", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterMessageServiceServer(s, &MessageServer{})
|
||||
|
||||
slog.Info("Starting the gRPC server...", "addr", lis.Addr())
|
||||
if err := s.Serve(lis); err != nil {
|
||||
slog.Error("grpc: server failed", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
|
||||
"git.dubyatp.xyz/dubyatp/scannerbot/server/api"
|
||||
grpcserver "git.dubyatp.xyz/dubyatp/scannerbot/server/grpc"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
@@ -35,6 +36,8 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
go grpcserver.Start()
|
||||
|
||||
slog.Info("Starting the API server...")
|
||||
api.Start()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.21.12
|
||||
// source: proto/message.proto
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
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 SendMessageRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"`
|
||||
Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
|
||||
AudioFilename string `protobuf:"bytes,3,opt,name=audio_filename,json=audioFilename,proto3" json:"audio_filename,omitempty"`
|
||||
Audio []byte `protobuf:"bytes,4,opt,name=audio,proto3" json:"audio,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) Reset() {
|
||||
*x = SendMessageRequest{}
|
||||
mi := &file_proto_message_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SendMessageRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SendMessageRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_message_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SendMessageRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SendMessageRequest) Descriptor() ([]byte, []int) {
|
||||
return file_proto_message_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) GetChannelId() string {
|
||||
if x != nil {
|
||||
return x.ChannelId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) GetContent() string {
|
||||
if x != nil {
|
||||
return x.Content
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) GetAudioFilename() string {
|
||||
if x != nil {
|
||||
return x.AudioFilename
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageRequest) GetAudio() []byte {
|
||||
if x != nil {
|
||||
return x.Audio
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SendMessageResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"`
|
||||
Created string `protobuf:"bytes,3,opt,name=created,proto3" json:"created,omitempty"`
|
||||
Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"`
|
||||
AudioId string `protobuf:"bytes,5,opt,name=audio_id,json=audioId,proto3" json:"audio_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) Reset() {
|
||||
*x = SendMessageResponse{}
|
||||
mi := &file_proto_message_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SendMessageResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SendMessageResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_message_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SendMessageResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SendMessageResponse) Descriptor() ([]byte, []int) {
|
||||
return file_proto_message_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) GetChannelId() string {
|
||||
if x != nil {
|
||||
return x.ChannelId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) GetCreated() string {
|
||||
if x != nil {
|
||||
return x.Created
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) GetContent() string {
|
||||
if x != nil {
|
||||
return x.Content
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SendMessageResponse) GetAudioId() string {
|
||||
if x != nil {
|
||||
return x.AudioId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_proto_message_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proto_message_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x13proto/message.proto\x12\n" +
|
||||
"scannerbot\"\x8a\x01\n" +
|
||||
"\x12SendMessageRequest\x12\x1d\n" +
|
||||
"\n" +
|
||||
"channel_id\x18\x01 \x01(\tR\tchannelId\x12\x18\n" +
|
||||
"\acontent\x18\x02 \x01(\tR\acontent\x12%\n" +
|
||||
"\x0eaudio_filename\x18\x03 \x01(\tR\raudioFilename\x12\x14\n" +
|
||||
"\x05audio\x18\x04 \x01(\fR\x05audio\"\x93\x01\n" +
|
||||
"\x13SendMessageResponse\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" +
|
||||
"\n" +
|
||||
"channel_id\x18\x02 \x01(\tR\tchannelId\x12\x18\n" +
|
||||
"\acreated\x18\x03 \x01(\tR\acreated\x12\x18\n" +
|
||||
"\acontent\x18\x04 \x01(\tR\acontent\x12\x19\n" +
|
||||
"\baudio_id\x18\x05 \x01(\tR\aaudioId2`\n" +
|
||||
"\x0eMessageService\x12N\n" +
|
||||
"\vSendMessage\x12\x1e.scannerbot.SendMessageRequest\x1a\x1f.scannerbot.SendMessageResponseB1Z/git.dubyatp.xyz/dubyatp/scannerbot/server/protob\x06proto3"
|
||||
|
||||
var (
|
||||
file_proto_message_proto_rawDescOnce sync.Once
|
||||
file_proto_message_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_proto_message_proto_rawDescGZIP() []byte {
|
||||
file_proto_message_proto_rawDescOnce.Do(func() {
|
||||
file_proto_message_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_message_proto_rawDesc), len(file_proto_message_proto_rawDesc)))
|
||||
})
|
||||
return file_proto_message_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_message_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_proto_message_proto_goTypes = []any{
|
||||
(*SendMessageRequest)(nil), // 0: scannerbot.SendMessageRequest
|
||||
(*SendMessageResponse)(nil), // 1: scannerbot.SendMessageResponse
|
||||
}
|
||||
var file_proto_message_proto_depIdxs = []int32{
|
||||
0, // 0: scannerbot.MessageService.SendMessage:input_type -> scannerbot.SendMessageRequest
|
||||
1, // 1: scannerbot.MessageService.SendMessage:output_type -> scannerbot.SendMessageResponse
|
||||
1, // [1:2] is the sub-list for method output_type
|
||||
0, // [0:1] 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_proto_message_proto_init() }
|
||||
func file_proto_message_proto_init() {
|
||||
if File_proto_message_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_message_proto_rawDesc), len(file_proto_message_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_proto_message_proto_goTypes,
|
||||
DependencyIndexes: file_proto_message_proto_depIdxs,
|
||||
MessageInfos: file_proto_message_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_message_proto = out.File
|
||||
file_proto_message_proto_goTypes = nil
|
||||
file_proto_message_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package scannerbot;
|
||||
|
||||
option go_package = "git.dubyatp.xyz/dubyatp/scannerbot/server/proto";
|
||||
|
||||
service MessageService {
|
||||
rpc SendMessage(SendMessageRequest) returns (SendMessageResponse);
|
||||
}
|
||||
|
||||
message SendMessageRequest {
|
||||
string channel_id = 1;
|
||||
string content = 2;
|
||||
string audio_filename = 3;
|
||||
bytes audio = 4;
|
||||
}
|
||||
|
||||
message SendMessageResponse {
|
||||
string id = 1;
|
||||
string channel_id = 2;
|
||||
string created = 3;
|
||||
string content = 4;
|
||||
string audio_id = 5;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v3.21.12
|
||||
// source: proto/message.proto
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
MessageService_SendMessage_FullMethodName = "/scannerbot.MessageService/SendMessage"
|
||||
)
|
||||
|
||||
// MessageServiceClient is the client API for MessageService 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 MessageServiceClient interface {
|
||||
SendMessage(ctx context.Context, in *SendMessageRequest, opts ...grpc.CallOption) (*SendMessageResponse, error)
|
||||
}
|
||||
|
||||
type messageServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewMessageServiceClient(cc grpc.ClientConnInterface) MessageServiceClient {
|
||||
return &messageServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *messageServiceClient) SendMessage(ctx context.Context, in *SendMessageRequest, opts ...grpc.CallOption) (*SendMessageResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(SendMessageResponse)
|
||||
err := c.cc.Invoke(ctx, MessageService_SendMessage_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MessageServiceServer is the server API for MessageService service.
|
||||
// All implementations must embed UnimplementedMessageServiceServer
|
||||
// for forward compatibility.
|
||||
type MessageServiceServer interface {
|
||||
SendMessage(context.Context, *SendMessageRequest) (*SendMessageResponse, error)
|
||||
mustEmbedUnimplementedMessageServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedMessageServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedMessageServiceServer struct{}
|
||||
|
||||
func (UnimplementedMessageServiceServer) SendMessage(context.Context, *SendMessageRequest) (*SendMessageResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method SendMessage not implemented")
|
||||
}
|
||||
func (UnimplementedMessageServiceServer) mustEmbedUnimplementedMessageServiceServer() {}
|
||||
func (UnimplementedMessageServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeMessageServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to MessageServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeMessageServiceServer interface {
|
||||
mustEmbedUnimplementedMessageServiceServer()
|
||||
}
|
||||
|
||||
func RegisterMessageServiceServer(s grpc.ServiceRegistrar, srv MessageServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedMessageServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&MessageService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _MessageService_SendMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SendMessageRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MessageServiceServer).SendMessage(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: MessageService_SendMessage_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MessageServiceServer).SendMessage(ctx, req.(*SendMessageRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// MessageService_ServiceDesc is the grpc.ServiceDesc for MessageService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var MessageService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "scannerbot.MessageService",
|
||||
HandlerType: (*MessageServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "SendMessage",
|
||||
Handler: _MessageService_SendMessage_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "proto/message.proto",
|
||||
}
|
||||
Reference in New Issue
Block a user