From ad7bc9f7dd6d3085e5925dfd103029a8108dad0d Mon Sep 17 00:00:00 2001 From: Fedorov Vladimir Date: Thu, 26 Mar 2026 01:56:29 +0700 Subject: [PATCH] add parser --- .vscode/launch.json | 20 +++ .vscode/settings.json | 11 ++ cmd/pinned_message/main.go | 31 +++- data/schedule.json | 1 + go.mod | 2 +- internal/app/server.go | 4 +- internal/config/config.go | 37 +++++ internal/models/day.go | 32 ++++ internal/modules/data_parser/interface.go | 7 + internal/modules/data_parser/parser.go | 42 +++++ internal/modules/date_parser/interface.go | 7 + internal/modules/date_parser/parser.go | 53 ++++++ internal/services/schedule_parser/service.go | 126 ++++++++++++++ internal/services/schedule_storage/service.go | 32 ++++ proto/main.pb.go | 157 ++++++++++++++++++ proto/main.pb.gw.go | 151 +++++++++++++++++ proto/main.swagger.json | 75 +++++++++ proto/main_grpc.pb.go | 121 ++++++++++++++ 18 files changed, 899 insertions(+), 10 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100755 data/schedule.json create mode 100644 internal/config/config.go create mode 100644 internal/models/day.go create mode 100644 internal/modules/data_parser/interface.go create mode 100644 internal/modules/data_parser/parser.go create mode 100644 internal/modules/date_parser/interface.go create mode 100644 internal/modules/date_parser/parser.go create mode 100644 internal/services/schedule_parser/service.go create mode 100644 internal/services/schedule_storage/service.go create mode 100644 proto/main.pb.go create mode 100644 proto/main.pb.gw.go create mode 100644 proto/main.swagger.json create mode 100644 proto/main_grpc.pb.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..38b0ae8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/cmd/pinned_message", + "args": [ + "--local" + ], + "cwd": "${workspaceFolder}", + "buildFlags": "-tags local" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8ffea0a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "cSpell.words": [ + "День", + "Номер", + "Номера", + "Событие", + "событиями", + "gocarina", + "gocsv" + ] +} \ No newline at end of file diff --git a/cmd/pinned_message/main.go b/cmd/pinned_message/main.go index 32a92f1..01b3952 100644 --- a/cmd/pinned_message/main.go +++ b/cmd/pinned_message/main.go @@ -1,15 +1,21 @@ package main import ( - "pinned_message/internal/app" - proto "pinned_message/proto" "context" - "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" "log" "net" "net/http" + "pinned_message/internal/app" + "pinned_message/internal/config" + "pinned_message/internal/modules/data_parser" + "pinned_message/internal/modules/date_parser" + "pinned_message/internal/services/schedule_parser" + "pinned_message/internal/services/schedule_storage" + proto "pinned_message/proto" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) func main() { @@ -21,8 +27,19 @@ func main() { // Create a gRPC server object s := grpc.NewServer() + + ctx := context.Background() + scheduleParser := schedule_parser.NewScheduleParser( + data_parser.NewGoogleTableScheduleParser(), + date_parser.NewDateParser(), + *schedule_storage.NewScheduleStorage(config.GetScheduleFilepath()), + ) + go func() { + scheduleParser.Run(ctx) + }() + // Attach the Greeter service to the server - proto.RegisterpinnedMessageServer(s, app.NewServer()) + proto.RegisterPinnedMessageServer(s, app.NewServer()) // Serve gRPC server log.Println("Serving gRPC on 0.0.0.0:8080") go func() { @@ -41,7 +58,7 @@ func main() { gwmux := runtime.NewServeMux() // Register Greeter - err = proto.RegisterpinnedMessageHandler(context.Background(), gwmux, conn) + err = proto.RegisterPinnedMessageHandler(context.Background(), gwmux, conn) if err != nil { log.Fatalln("Failed to register gateway:", err) } diff --git a/data/schedule.json b/data/schedule.json new file mode 100755 index 0000000..07eab63 --- /dev/null +++ b/data/schedule.json @@ -0,0 +1 @@ +[{"date":"2026-03-24T00:00:00+07:00","performances":[{"time_collection":"-","time_start":"18:00 - 20:00","place":"танцкласс","name":"репетиция","numbers":[{"name":"все"}],"costumes":"-"}]},{"date":"2026-03-26T00:00:00+07:00","performances":[{"time_collection":"-","time_start":"18:00 - 20:00","place":"танцкласс","name":"репетиция","numbers":[{"name":"все"}],"costumes":"-"}]},{"date":"2026-03-27T00:00:00+07:00","performances":[{"time_collection":"13:00","time_start":"15:00 - 17:00","place":"КДМ (Юрина 204в)","name":"Закрытие регионального этапа Всероссийского проекта-фестиваля «Российская школьная весна» Алтайского края и Открытие Краевого фестиваля студенческого творчества \"Студенческая весна на Алтае. Феста - 2026\"","numbers":[{"name":"Гавря"}],"costumes":""}]},{"date":"2026-03-28T00:00:00+07:00","performances":[{"time_collection":"","time_start":"17:30 - 21:00","place":"актовый зал С","name":"репетиция КП","numbers":[{"name":"Еще люблю"}],"costumes":"реп форма"}]},{"date":"2026-03-29T00:00:00+07:00","performances":[{"time_collection":"12.15","time_start":"13:00-14:00","place":"концертный зал Д","name":"День открытых дверей АлтГУ","numbers":[{"name":"Русская душа"}],"costumes":""}]},{"date":"2026-03-30T00:00:00+07:00","performances":[{"time_collection":"","time_start":"11:30-15:00","place":"аграрка на красноармейском","name":"ФЕСТА. Оригинальный жанр","numbers":[{"name":"Песня цыганки"}],"costumes":""}]},{"date":"2026-03-31T00:00:00+07:00","performances":[{"time_collection":"","time_start":"17:30 - 21:00","place":"актовый зал С","name":"репетиция КП","numbers":[{"name":"Еще люблю"}],"costumes":"реп форма"}]},{"date":"2026-04-01T00:00:00+07:00","performances":[{"time_collection":"","time_start":"17:30 - 21:00","place":"актовый зал С","name":"репетиция КП","numbers":[{"name":"Еще люблю"}],"costumes":"костюм свой"}]},{"date":"2026-04-02T00:00:00+07:00","performances":[{"time_collection":"","time_start":"17:30 - 21:00","place":"актовый зал С","name":"репетиция КП","numbers":[{"name":"Еще люблю"}],"costumes":"костюм свой"}]},{"date":"2026-04-03T00:00:00+07:00","performances":[{"time_collection":"","time_start":"10:00-11:00","place":"актовый зал С","name":"ФЕСТА. КП","numbers":[{"name":"Еще люблю"}],"costumes":"костюм свой"},{"time_collection":"","time_start":"15:00-16:00","place":"актовый зал С","name":"репетиция танцевального направления","numbers":[{"name":"Русская душа"},{"name":" Гавря"},{"name":" Полька"}],"costumes":""}]},{"date":"2026-04-04T00:00:00+07:00","performances":[{"time_collection":"","time_start":"","place":"актовый зал С","name":"ФЕСТА. Танцевальное направление","numbers":[{"name":"Русская душа"},{"name":" Гавря"},{"name":" Полька"}],"costumes":""}]},{"date":"2026-04-05T00:00:00+07:00","performances":[{"time_collection":"","time_start":"","place":"актовый зал С","name":"ФЕСТА. Танцевальное направление","numbers":[{"name":"Русская душа"},{"name":" Гавря"},{"name":" Полька"}],"costumes":""}]},{"date":"2026-04-06T00:00:00+07:00","performances":[{"time_collection":"","time_start":"","place":"концертный зал Д","name":"ЕММО. открытие","numbers":[{"name":"Китайский"},{"name":" Полька"}],"costumes":""}]},{"date":"2026-04-08T00:00:00+07:00","performances":[{"time_collection":"","time_start":"","place":"концертный зал Д","name":"ЕММО. закрытие","numbers":[{"name":"Гавря"},{"name":" Русская душа"}],"costumes":""}]},{"date":"2026-04-17T00:00:00+07:00","performances":[{"time_collection":"","time_start":"Вечер","place":"актовый зал С","name":"Мисс ИББ","numbers":[{"name":"Китайский"}],"costumes":""}]},{"date":"2026-04-24T00:00:00+07:00","performances":[{"time_collection":"","time_start":"","place":"актовый зал С","name":"Отчетник","numbers":[{"name":"мы сдохнем"}],"costumes":""}]},{"date":"2026-05-01T00:00:00+07:00","performances":[{"time_collection":"","time_start":"","place":"отъезд от гаража С","name":"Маральник","numbers":[{"name":""}],"costumes":""}]}] \ No newline at end of file diff --git a/go.mod b/go.mod index 5b5feac..4ee62e1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module pinned_message go 1.22 require ( - github.com/go-pkgz/routegroup v1.1.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 google.golang.org/grpc v1.64.0 @@ -11,6 +10,7 @@ require ( ) require ( + github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/internal/app/server.go b/internal/app/server.go index 9d2add8..9f3a4ee 100644 --- a/internal/app/server.go +++ b/internal/app/server.go @@ -1,12 +1,12 @@ package app import ( - proto "pinned_message/proto" "context" + proto "pinned_message/proto" ) type Server struct { - proto.UnimplementedpinnedMessageServer + proto.UnimplementedPinnedMessageServer } func NewServer() *Server { diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..ef830af --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,37 @@ +package config + +import ( + "os" + "path/filepath" +) + +const ( + ClientPort = ":8100" + FilePort = ":8120" +) + +func GetScheduleFilepath() string { + return getFilepath("SCHEDULE_FILENAME", "data/schedule.json") +} + +func getFilepath(env string, defaultFilepath string) string { + filepath := selectFilepath(env, defaultFilepath) + ensureDirExists(filepath) + return filepath +} + +func selectFilepath(env string, defaultFilepath string) string { + filepath := os.Getenv(env) + if filepath != "" { + return filepath + } + return defaultFilepath +} + +func ensureDirExists(filePath string) error { + dir := filepath.Dir(filePath) + if dir == "" || dir == "." || dir == "/" { + return nil + } + return os.MkdirAll(dir, 0755) +} diff --git a/internal/models/day.go b/internal/models/day.go new file mode 100644 index 0000000..1cb1673 --- /dev/null +++ b/internal/models/day.go @@ -0,0 +1,32 @@ +package models + +import "time" + +// День с событиями +type Day struct { + Date time.Time `json:"date"` + Performances []*DayPerformance `json:"performances"` +} + +// Событие (мероприятия, праздник, поездка, событие) +type DayPerformance struct { + // Время сбора + TimeCollection string `json:"time_collection"` + // Время начала мероприятия + TimeStart string `json:"time_start"` + // Место + Place string `json:"place"` + + // Наименование мероприятия + Name string `json:"name"` + // Номера + Numbers []*Number `json:"numbers"` + // Костюмы + Costumes string `json:"costumes"` +} + +// Номер +type Number struct { + // Название + Name string `json:"name"` +} diff --git a/internal/modules/data_parser/interface.go b/internal/modules/data_parser/interface.go new file mode 100644 index 0000000..2ec4cb9 --- /dev/null +++ b/internal/modules/data_parser/interface.go @@ -0,0 +1,7 @@ +package data_parser + +import "context" + +type IDataParser interface { + Parse(ctx context.Context, url string, v interface{}) error +} diff --git a/internal/modules/data_parser/parser.go b/internal/modules/data_parser/parser.go new file mode 100644 index 0000000..70edace --- /dev/null +++ b/internal/modules/data_parser/parser.go @@ -0,0 +1,42 @@ +package data_parser + +import ( + "context" + "fmt" + "net/http" + "regexp" + + "github.com/gocarina/gocsv" +) + +type parser struct{} + +func NewGoogleTableScheduleParser() IDataParser { + return &parser{} +} + +func (p *parser) Parse(_ context.Context, url string, v interface{}) error { + re := regexp.MustCompile(`/d/([a-zA-Z0-9-_]+)`) + matches := re.FindStringSubmatch(url) + if len(matches) < 2 { + return fmt.Errorf("Не удалось найти ID таблицы в ссылке") + } + sheetID := matches[1] + + csvURL := fmt.Sprintf("https://docs.google.com/spreadsheets/d/%s/export?format=csv", sheetID) + + resp, err := http.Get(csvURL) + if err != nil { + return fmt.Errorf("Ошибка при скачивании таблицы: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("Ошибка: статус код %d (убедитесь, что таблица публичная)", resp.StatusCode) + } + + if err := gocsv.Unmarshal(resp.Body, v); err != nil { + return fmt.Errorf("Ошибка при парсинге CSV в структуру: %v", err) + } + return nil +} diff --git a/internal/modules/date_parser/interface.go b/internal/modules/date_parser/interface.go new file mode 100644 index 0000000..8dc2fdc --- /dev/null +++ b/internal/modules/date_parser/interface.go @@ -0,0 +1,7 @@ +package date_parser + +import "time" + +type IDateParser interface { + Parse(date string) (time.Time, error) +} diff --git a/internal/modules/date_parser/parser.go b/internal/modules/date_parser/parser.go new file mode 100644 index 0000000..1320c5b --- /dev/null +++ b/internal/modules/date_parser/parser.go @@ -0,0 +1,53 @@ +package date_parser + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +var ( + ruMonths = map[string]time.Month{ + "января": time.January, + "февраля": time.February, + "марта": time.March, + "апреля": time.April, + "мая": time.May, + "июня": time.June, + "июля": time.July, + "августа": time.August, + "сентября": time.September, + "октября": time.October, + "ноября": time.November, + "декабря": time.December, + } +) + +type parser struct{} + +func NewDateParser() IDateParser { + return &parser{} +} + +func (p *parser) Parse(date string) (time.Time, error) { + parts := strings.Fields(date) + if len(parts) < 2 { + return time.Time{}, fmt.Errorf("Неверный формат даты, ожидалось 'День Месяц', получено: '%s'", date) + } + + day, err := strconv.Atoi(parts[0]) + if err != nil { + return time.Time{}, fmt.Errorf("Не удалось получить день: %v", err) + } + + monthStr := strings.ToLower(parts[1]) + month, ok := ruMonths[monthStr] + if !ok { + return time.Time{}, fmt.Errorf("Неизвестный месяц: %s", monthStr) + } + + year := time.Now().Year() + + return time.Date(year, month, day, 0, 0, 0, 0, time.Local), nil +} diff --git a/internal/services/schedule_parser/service.go b/internal/services/schedule_parser/service.go new file mode 100644 index 0000000..35dc80f --- /dev/null +++ b/internal/services/schedule_parser/service.go @@ -0,0 +1,126 @@ +package schedule_parser + +import ( + "context" + "log" + "pinned_message/internal/models" + "pinned_message/internal/modules/data_parser" + "pinned_message/internal/modules/date_parser" + "pinned_message/internal/services/schedule_storage" + "strings" + "time" +) + +type performance struct { + Date string `csv:"ДАТА"` + Day string `csv:"день недели"` + Name string `csv:"НАЗВАНИЕ"` + Place string `csv:"МЕСТО"` + TimeCollection string `csv:"время СБОРА (для концерта)"` + TimeStart string `csv:"время НАЧАЛА"` + Numbers string `csv:"ЧТО ТАНЦУЕМ"` + Costumes string `csv:"КОСТЮМЫ"` +} + +type ScheduleParser struct { + dataParser data_parser.IDataParser + dateParser date_parser.IDateParser + scheduleStorage schedule_storage.ScheduleStorage +} + +func NewScheduleParser( + dataParser data_parser.IDataParser, + dateParser date_parser.IDateParser, + scheduleStorage schedule_storage.ScheduleStorage, +) *ScheduleParser { + return &ScheduleParser{ + dataParser: dataParser, + dateParser: dateParser, + scheduleStorage: scheduleStorage, + } +} + +func (p *ScheduleParser) Run(ctx context.Context) { + ticker := time.NewTicker(15 * time.Second) // TODO: set 1h + defer ticker.Stop() + + sheetURL := "https://docs.google.com/spreadsheets/d/1v57bCAG764j1ULXDMb3amNFMzkkLmObKWsl5oE0Xq00/edit?gid=57461713#gid=57461713" + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + days, err := p.parseSchedule(ctx, sheetURL) + if err != nil { + log.Printf("Error parse schedule: %s\n", sheetURL) + break + } + if err := p.scheduleStorage.SaveSchedule(days); err != nil { + log.Printf("Error save err: %s schedule: %s\n", err, sheetURL) + } + } + } +} + +func (p *ScheduleParser) parseSchedule(ctx context.Context, sheetURL string) ([]*models.Day, error) { + var performances []performance + if err := p.dataParser.Parse(ctx, sheetURL, &performances); err != nil { + return nil, err + } + return p.mapSchedule(performances), nil +} + +func (p *ScheduleParser) mapSchedule(performances []performance) []*models.Day { + days := []*models.Day{} + currentDay := &models.Day{} + for i, performance := range performances { + if performance.Name == "" || performance.Name == "-" { + continue + } + if performance.Date != "" { + if i > 0 { + days = append(days, currentDay) + currentDay = &models.Day{} + } + date, err := p.mapDate(performance.Date) + if err != nil { + panic(err) + } + currentDay.Date = date + } + currentDay.Performances = append( + currentDay.Performances, + &models.DayPerformance{ + TimeCollection: performance.TimeCollection, + TimeStart: performance.TimeStart, + Place: performance.Place, + Name: performance.Name, + Numbers: p.mapNumbers(performance.Numbers), + Costumes: performance.Costumes, + }, + ) + } + days = append(days, currentDay) + return days +} + +func (p *ScheduleParser) mapDate(date string) (time.Time, error) { + if date == "" { + return time.Time{}, nil + } + return p.dateParser.Parse(date) +} + +func (p *ScheduleParser) mapNumbers(numbers string) []*models.Number { + names := strings.Split(numbers, ",") + res := make([]*models.Number, 0, len(names)) + for _, name := range names { + res = append( + res, + &models.Number{ + Name: name, + }, + ) + } + return res +} diff --git a/internal/services/schedule_storage/service.go b/internal/services/schedule_storage/service.go new file mode 100644 index 0000000..93f6052 --- /dev/null +++ b/internal/services/schedule_storage/service.go @@ -0,0 +1,32 @@ +package schedule_storage + +import ( + "encoding/json" + "log" + "os" + "pinned_message/internal/models" +) + +type ScheduleStorage struct { + filepath string +} + +func NewScheduleStorage( + filepath string, +) *ScheduleStorage { + return &ScheduleStorage{ + filepath: filepath, + } +} + +func (s *ScheduleStorage) SaveSchedule(days []*models.Day) error { + data, err := json.Marshal(days) + if err != nil { + return err + } + if err := os.WriteFile(s.filepath, data, 0x777); err != nil { + return err + } + log.Printf("save story to: %s", s.filepath) + return nil +} diff --git a/proto/main.pb.go b/proto/main.pb.go new file mode 100644 index 0000000..813278b --- /dev/null +++ b/proto/main.pb.go @@ -0,0 +1,157 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.9 +// protoc v6.32.1 +// source: main.proto + +package proto + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + 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 PingReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingReq) Reset() { + *x = PingReq{} + mi := &file_main_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingReq) ProtoMessage() {} + +func (x *PingReq) ProtoReflect() protoreflect.Message { + mi := &file_main_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 PingReq.ProtoReflect.Descriptor instead. +func (*PingReq) Descriptor() ([]byte, []int) { + return file_main_proto_rawDescGZIP(), []int{0} +} + +type PingRsp struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PingRsp) Reset() { + *x = PingRsp{} + mi := &file_main_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PingRsp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRsp) ProtoMessage() {} + +func (x *PingRsp) ProtoReflect() protoreflect.Message { + mi := &file_main_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 PingRsp.ProtoReflect.Descriptor instead. +func (*PingRsp) Descriptor() ([]byte, []int) { + return file_main_proto_rawDescGZIP(), []int{1} +} + +var File_main_proto protoreflect.FileDescriptor + +const file_main_proto_rawDesc = "" + + "\n" + + "\n" + + "main.proto\x12\x14crabs.pinned_message\x1a\x1cgoogle/api/annotations.proto\"\t\n" + + "\aPingReq\"\t\n" + + "\aPingRsp2d\n" + + "\rpinnedMessage\x12S\n" + + "\x04Ping\x12\x1d.crabs.pinned_message.PingReq\x1a\x1d.crabs.pinned_message.PingRsp\"\r\x82\xd3\xe4\x93\x02\a\x12\x05/pingB\vZ\tpkg/protob\x06proto3" + +var ( + file_main_proto_rawDescOnce sync.Once + file_main_proto_rawDescData []byte +) + +func file_main_proto_rawDescGZIP() []byte { + file_main_proto_rawDescOnce.Do(func() { + file_main_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_main_proto_rawDesc), len(file_main_proto_rawDesc))) + }) + return file_main_proto_rawDescData +} + +var file_main_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_main_proto_goTypes = []any{ + (*PingReq)(nil), // 0: crabs.pinned_message.PingReq + (*PingRsp)(nil), // 1: crabs.pinned_message.PingRsp +} +var file_main_proto_depIdxs = []int32{ + 0, // 0: crabs.pinned_message.pinnedMessage.Ping:input_type -> crabs.pinned_message.PingReq + 1, // 1: crabs.pinned_message.pinnedMessage.Ping:output_type -> crabs.pinned_message.PingRsp + 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_main_proto_init() } +func file_main_proto_init() { + if File_main_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_main_proto_rawDesc), len(file_main_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_main_proto_goTypes, + DependencyIndexes: file_main_proto_depIdxs, + MessageInfos: file_main_proto_msgTypes, + }.Build() + File_main_proto = out.File + file_main_proto_goTypes = nil + file_main_proto_depIdxs = nil +} diff --git a/proto/main.pb.gw.go b/proto/main.pb.gw.go new file mode 100644 index 0000000..69c4c79 --- /dev/null +++ b/proto/main.pb.gw.go @@ -0,0 +1,151 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: main.proto + +/* +Package proto is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package proto + +import ( + "context" + "errors" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var ( + _ codes.Code + _ io.Reader + _ status.Status + _ = errors.New + _ = runtime.String + _ = utilities.NewDoubleArray + _ = metadata.Join +) + +func request_PinnedMessage_Ping_0(ctx context.Context, marshaler runtime.Marshaler, client PinnedMessageClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq PingReq + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.Ping(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_PinnedMessage_Ping_0(ctx context.Context, marshaler runtime.Marshaler, server PinnedMessageServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq PingReq + metadata runtime.ServerMetadata + ) + msg, err := server.Ping(ctx, &protoReq) + return msg, metadata, err +} + +// RegisterPinnedMessageHandlerServer registers the http handlers for service PinnedMessage to "mux". +// UnaryRPC :call PinnedMessageServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterPinnedMessageHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterPinnedMessageHandlerServer(ctx context.Context, mux *runtime.ServeMux, server PinnedMessageServer) error { + mux.Handle(http.MethodGet, pattern_PinnedMessage_Ping_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/crabs.pinned_message.PinnedMessage/Ping", runtime.WithHTTPPathPattern("/ping")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_PinnedMessage_Ping_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_PinnedMessage_Ping_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + + return nil +} + +// RegisterPinnedMessageHandlerFromEndpoint is same as RegisterPinnedMessageHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterPinnedMessageHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + return RegisterPinnedMessageHandler(ctx, mux, conn) +} + +// RegisterPinnedMessageHandler registers the http handlers for service PinnedMessage to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterPinnedMessageHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterPinnedMessageHandlerClient(ctx, mux, NewPinnedMessageClient(conn)) +} + +// RegisterPinnedMessageHandlerClient registers the http handlers for service PinnedMessage +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "PinnedMessageClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "PinnedMessageClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "PinnedMessageClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterPinnedMessageHandlerClient(ctx context.Context, mux *runtime.ServeMux, client PinnedMessageClient) error { + mux.Handle(http.MethodGet, pattern_PinnedMessage_Ping_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/crabs.pinned_message.PinnedMessage/Ping", runtime.WithHTTPPathPattern("/ping")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_PinnedMessage_Ping_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_PinnedMessage_Ping_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + return nil +} + +var ( + pattern_PinnedMessage_Ping_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"ping"}, "")) +) + +var ( + forward_PinnedMessage_Ping_0 = runtime.ForwardResponseMessage +) diff --git a/proto/main.swagger.json b/proto/main.swagger.json new file mode 100644 index 0000000..79c5742 --- /dev/null +++ b/proto/main.swagger.json @@ -0,0 +1,75 @@ +{ + "swagger": "2.0", + "info": { + "title": "main.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/ping": { + "get": { + "operationId": "pinnedMessage_Ping", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pinned_messagePingRsp" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "tags": [ + "pinnedMessage" + ] + } + } + }, + "definitions": { + "pinned_messagePingRsp": { + "type": "object" + }, + "protobufAny": { + "type": "object", + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "runtimeError": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/proto/main_grpc.pb.go b/proto/main_grpc.pb.go new file mode 100644 index 0000000..f3403d6 --- /dev/null +++ b/proto/main_grpc.pb.go @@ -0,0 +1,121 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v6.32.1 +// source: main.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 ( + PinnedMessage_Ping_FullMethodName = "/crabs.pinned_message.pinnedMessage/Ping" +) + +// PinnedMessageClient is the client API for PinnedMessage 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 PinnedMessageClient interface { + Ping(ctx context.Context, in *PingReq, opts ...grpc.CallOption) (*PingRsp, error) +} + +type pinnedMessageClient struct { + cc grpc.ClientConnInterface +} + +func NewPinnedMessageClient(cc grpc.ClientConnInterface) PinnedMessageClient { + return &pinnedMessageClient{cc} +} + +func (c *pinnedMessageClient) Ping(ctx context.Context, in *PingReq, opts ...grpc.CallOption) (*PingRsp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PingRsp) + err := c.cc.Invoke(ctx, PinnedMessage_Ping_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PinnedMessageServer is the server API for PinnedMessage service. +// All implementations must embed UnimplementedPinnedMessageServer +// for forward compatibility. +type PinnedMessageServer interface { + Ping(context.Context, *PingReq) (*PingRsp, error) + mustEmbedUnimplementedPinnedMessageServer() +} + +// UnimplementedPinnedMessageServer 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 UnimplementedPinnedMessageServer struct{} + +func (UnimplementedPinnedMessageServer) Ping(context.Context, *PingReq) (*PingRsp, error) { + return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") +} +func (UnimplementedPinnedMessageServer) mustEmbedUnimplementedPinnedMessageServer() {} +func (UnimplementedPinnedMessageServer) testEmbeddedByValue() {} + +// UnsafePinnedMessageServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PinnedMessageServer will +// result in compilation errors. +type UnsafePinnedMessageServer interface { + mustEmbedUnimplementedPinnedMessageServer() +} + +func RegisterPinnedMessageServer(s grpc.ServiceRegistrar, srv PinnedMessageServer) { + // If the following call pancis, it indicates UnimplementedPinnedMessageServer 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(&PinnedMessage_ServiceDesc, srv) +} + +func _PinnedMessage_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PingReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PinnedMessageServer).Ping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PinnedMessage_Ping_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PinnedMessageServer).Ping(ctx, req.(*PingReq)) + } + return interceptor(ctx, in, info, handler) +} + +// PinnedMessage_ServiceDesc is the grpc.ServiceDesc for PinnedMessage service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PinnedMessage_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "crabs.pinned_message.pinnedMessage", + HandlerType: (*PinnedMessageServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Ping", + Handler: _PinnedMessage_Ping_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "main.proto", +}