From 42172fb0a505defa091c3487ba72e9b5856337bb Mon Sep 17 00:00:00 2001 From: Elmar Kresse Date: Fri, 25 Oct 2024 00:16:02 +0200 Subject: [PATCH] feat:#16 added room ics api route --- docker-compose.yml | 5 +- reverseproxy.conf | 18 ++ reverseproxy.dev.conf | 18 ++ services/common/genproto/modules/room.pb.go | 225 ++++++++++++++++++ .../common/genproto/modules/room_grpc.pb.go | 105 ++++++++ services/data-manager/service/db/dbRooms.go | 16 +- .../data-manager/service/grpc/roomService.go | 36 +++ services/data-manager/service/grpc/server.go | 4 + .../data-manager/service/room/roomService.go | 2 +- .../ical/service/connector/grpc/events.go | 20 +- services/ical/service/ical/ical.go | 22 +- services/ical/service/routes.go | 21 ++ services/protobuf/room.proto | 19 ++ 13 files changed, 505 insertions(+), 6 deletions(-) create mode 100644 services/common/genproto/modules/room.pb.go create mode 100644 services/common/genproto/modules/room_grpc.pb.go create mode 100644 services/data-manager/service/grpc/roomService.go create mode 100644 services/protobuf/room.proto diff --git a/docker-compose.yml b/docker-compose.yml index 97cd99c..c78b9ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,8 +21,9 @@ services: context: ./services target: dev # prod command: "--http=0.0.0.0:8090 --dir=/htwkalender-data-manager/data/pb_data" - #ports: - # - "8090:8090" + ports: + - "8090:8090" + - "50051:50051" volumes: - pb_data:/htwkalender-data-manager/data # for production with volume # - ./data-manager:/htwkalender/data # for development with bind mount from project directory diff --git a/reverseproxy.conf b/reverseproxy.conf index 71332dc..ebcb0d0 100644 --- a/reverseproxy.conf +++ b/reverseproxy.conf @@ -193,6 +193,24 @@ http { ssl_certificate cal.htwk-leipzig.de.pem; ssl_certificate_key cal.htwk-leipzig.de.key.pem; + location /api/feed/room { + proxy_pass http://htwkalender-ical:8091; + client_max_body_size 2m; + proxy_connect_timeout 600s; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + send_timeout 600s; + proxy_cache_bypass 0; + proxy_no_cache 0; + proxy_cache mcache; # mcache=RAM + proxy_cache_valid 200 301 302 10m; + proxy_cache_valid 403 404 5m; + proxy_cache_lock on; + proxy_cache_use_stale timeout updating; + add_header X-Proxy-Cache $upstream_cache_status; + limit_req zone=modules burst=5 nodelay; + } + location /api/feed { limit_req zone=createFeed nodelay; limit_req zone=feed burst=10 nodelay; diff --git a/reverseproxy.dev.conf b/reverseproxy.dev.conf index d735cad..6eff75c 100644 --- a/reverseproxy.dev.conf +++ b/reverseproxy.dev.conf @@ -145,6 +145,24 @@ http { ssl_certificate dev_htwkalender_de.pem; ssl_certificate_key dev_htwkalender_de.key.pem; + location /api/feed/room { + proxy_pass http://htwkalender-ical:8091; + client_max_body_size 2m; + proxy_connect_timeout 600s; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + send_timeout 600s; + proxy_cache_bypass 0; + proxy_no_cache 0; + proxy_cache mcache; # mcache=RAM + proxy_cache_valid 200 301 302 10m; + proxy_cache_valid 403 404 5m; + proxy_cache_lock on; + proxy_cache_use_stale timeout updating; + add_header X-Proxy-Cache $upstream_cache_status; + limit_req zone=modules burst=5 nodelay; + } + location /api/feed { limit_req zone=createFeed nodelay; limit_req zone=feed burst=10 nodelay; diff --git a/services/common/genproto/modules/room.pb.go b/services/common/genproto/modules/room.pb.go new file mode 100644 index 0000000..ee169bf --- /dev/null +++ b/services/common/genproto/modules/room.pb.go @@ -0,0 +1,225 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: room.proto + +package modules + +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 GetRoomResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Room string `protobuf:"bytes,1,opt,name=room,proto3" json:"room,omitempty"` + Events []*Event `protobuf:"bytes,2,rep,name=events,proto3" json:"events,omitempty"` +} + +func (x *GetRoomResponse) Reset() { + *x = GetRoomResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_room_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRoomResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoomResponse) ProtoMessage() {} + +func (x *GetRoomResponse) ProtoReflect() protoreflect.Message { + mi := &file_room_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 GetRoomResponse.ProtoReflect.Descriptor instead. +func (*GetRoomResponse) Descriptor() ([]byte, []int) { + return file_room_proto_rawDescGZIP(), []int{0} +} + +func (x *GetRoomResponse) GetRoom() string { + if x != nil { + return x.Room + } + return "" +} + +func (x *GetRoomResponse) GetEvents() []*Event { + if x != nil { + return x.Events + } + return nil +} + +type GetRoomRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Room string `protobuf:"bytes,1,opt,name=room,proto3" json:"room,omitempty"` +} + +func (x *GetRoomRequest) Reset() { + *x = GetRoomRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_room_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRoomRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoomRequest) ProtoMessage() {} + +func (x *GetRoomRequest) ProtoReflect() protoreflect.Message { + mi := &file_room_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoomRequest.ProtoReflect.Descriptor instead. +func (*GetRoomRequest) Descriptor() ([]byte, []int) { + return file_room_proto_rawDescGZIP(), []int{1} +} + +func (x *GetRoomRequest) GetRoom() string { + if x != nil { + return x.Room + } + return "" +} + +var File_room_proto protoreflect.FileDescriptor + +var file_room_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0d, 0x6d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x45, 0x0a, 0x0f, 0x47, + 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, + 0x6f, 0x6d, 0x12, 0x1e, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x22, 0x24, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x32, 0x43, 0x0a, 0x0b, 0x52, 0x6f, 0x6f, 0x6d, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x6f, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x0f, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x47, 0x65, 0x74, 0x52, + 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x1c, 0x5a, + 0x1a, 0x68, 0x74, 0x77, 0x6b, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_room_proto_rawDescOnce sync.Once + file_room_proto_rawDescData = file_room_proto_rawDesc +) + +func file_room_proto_rawDescGZIP() []byte { + file_room_proto_rawDescOnce.Do(func() { + file_room_proto_rawDescData = protoimpl.X.CompressGZIP(file_room_proto_rawDescData) + }) + return file_room_proto_rawDescData +} + +var file_room_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_room_proto_goTypes = []interface{}{ + (*GetRoomResponse)(nil), // 0: GetRoomResponse + (*GetRoomRequest)(nil), // 1: GetRoomRequest + (*Event)(nil), // 2: Event +} +var file_room_proto_depIdxs = []int32{ + 2, // 0: GetRoomResponse.events:type_name -> Event + 1, // 1: RoomService.GetRoomEvents:input_type -> GetRoomRequest + 0, // 2: RoomService.GetRoomEvents:output_type -> GetRoomResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_room_proto_init() } +func file_room_proto_init() { + if File_room_proto != nil { + return + } + file_modules_proto_init() + if !protoimpl.UnsafeEnabled { + file_room_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRoomResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_room_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRoomRequest); 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_room_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_room_proto_goTypes, + DependencyIndexes: file_room_proto_depIdxs, + MessageInfos: file_room_proto_msgTypes, + }.Build() + File_room_proto = out.File + file_room_proto_rawDesc = nil + file_room_proto_goTypes = nil + file_room_proto_depIdxs = nil +} diff --git a/services/common/genproto/modules/room_grpc.pb.go b/services/common/genproto/modules/room_grpc.pb.go new file mode 100644 index 0000000..4c9cf48 --- /dev/null +++ b/services/common/genproto/modules/room_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.12 +// source: room.proto + +package modules + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// RoomServiceClient is the client API for RoomService 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 RoomServiceClient interface { + GetRoomEvents(ctx context.Context, in *GetRoomRequest, opts ...grpc.CallOption) (*GetRoomResponse, error) +} + +type roomServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewRoomServiceClient(cc grpc.ClientConnInterface) RoomServiceClient { + return &roomServiceClient{cc} +} + +func (c *roomServiceClient) GetRoomEvents(ctx context.Context, in *GetRoomRequest, opts ...grpc.CallOption) (*GetRoomResponse, error) { + out := new(GetRoomResponse) + err := c.cc.Invoke(ctx, "/RoomService/GetRoomEvents", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RoomServiceServer is the server API for RoomService service. +// All implementations must embed UnimplementedRoomServiceServer +// for forward compatibility +type RoomServiceServer interface { + GetRoomEvents(context.Context, *GetRoomRequest) (*GetRoomResponse, error) + mustEmbedUnimplementedRoomServiceServer() +} + +// UnimplementedRoomServiceServer must be embedded to have forward compatible implementations. +type UnimplementedRoomServiceServer struct { +} + +func (UnimplementedRoomServiceServer) GetRoomEvents(context.Context, *GetRoomRequest) (*GetRoomResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRoomEvents not implemented") +} +func (UnimplementedRoomServiceServer) mustEmbedUnimplementedRoomServiceServer() {} + +// UnsafeRoomServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RoomServiceServer will +// result in compilation errors. +type UnsafeRoomServiceServer interface { + mustEmbedUnimplementedRoomServiceServer() +} + +func RegisterRoomServiceServer(s grpc.ServiceRegistrar, srv RoomServiceServer) { + s.RegisterService(&RoomService_ServiceDesc, srv) +} + +func _RoomService_GetRoomEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoomRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomServiceServer).GetRoomEvents(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/RoomService/GetRoomEvents", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomServiceServer).GetRoomEvents(ctx, req.(*GetRoomRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// RoomService_ServiceDesc is the grpc.ServiceDesc for RoomService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RoomService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "RoomService", + HandlerType: (*RoomServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetRoomEvents", + Handler: _RoomService_GetRoomEvents_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "room.proto", +} diff --git a/services/data-manager/service/db/dbRooms.go b/services/data-manager/service/db/dbRooms.go index 45564c1..db97e57 100644 --- a/services/data-manager/service/db/dbRooms.go +++ b/services/data-manager/service/db/dbRooms.go @@ -94,7 +94,7 @@ func GetRoomScheduleForDay(app *pocketbase.PocketBase, room string, date string) return events, nil } -func GetRoomSchedule(app *pocketbase.PocketBase, room string, from string, to string) ([]model.Event, error) { +func GetRoomScheduleInTimeSpan(app *pocketbase.PocketBase, room string, from string, to string) ([]model.Event, error) { var events []model.Event fromDate, err := time.Parse("2006-01-02", from) @@ -118,3 +118,17 @@ func GetRoomSchedule(app *pocketbase.PocketBase, room string, from string, to st } return events, nil } + +func GetRoomSchedule(app *pocketbase.PocketBase, room string) ([]model.Event, error) { + var events []model.Event + + // get all events from event records in the events collection + err := app.Dao().DB().Select("*").From("events"). + Where(dbx.Like("Rooms", room).Escape("_", "_")). + GroupBy("Week", "Start", "End", "Rooms"). + All(&events) + if err != nil { + return nil, err + } + return events, nil +} diff --git a/services/data-manager/service/grpc/roomService.go b/services/data-manager/service/grpc/roomService.go new file mode 100644 index 0000000..5823b67 --- /dev/null +++ b/services/data-manager/service/grpc/roomService.go @@ -0,0 +1,36 @@ +package grpc + +import ( + "context" + "fmt" + "github.com/pocketbase/pocketbase" + pb "htwkalender/common/genproto/modules" + "htwkalender/data-manager/service/db" +) + +type RoomServiceHandler struct { + app *pocketbase.PocketBase + pb.UnimplementedRoomServiceServer +} + +func (s *RoomServiceHandler) GetRoomEvents(ctx context.Context, in *pb.GetRoomRequest) (*pb.GetRoomResponse, error) { + + s.app.Logger().Info( + "Protobuf - GetRoomEvents", + "room", in.Room, + ) + + fmt.Errorf("Getting events for room %s", in.Room) + + // get events from database by room + events, err := db.GetRoomSchedule(s.app, in.Room) + if err != nil { + return nil, err + } + + // Implement your logic here to fetch events data based on the room + // Example response + return &pb.GetRoomResponse{ + Events: eventsToProto(events), + }, nil +} diff --git a/services/data-manager/service/grpc/server.go b/services/data-manager/service/grpc/server.go index 9992ec0..a783ec2 100644 --- a/services/data-manager/service/grpc/server.go +++ b/services/data-manager/service/grpc/server.go @@ -39,6 +39,10 @@ func StartGRPCServer(app *pocketbase.PocketBase) { app: app, }) + pb.RegisterRoomServiceServer(s, &RoomServiceHandler{ + app: app, + }) + log.Printf("server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) diff --git a/services/data-manager/service/room/roomService.go b/services/data-manager/service/room/roomService.go index 7444309..e1dedcb 100644 --- a/services/data-manager/service/room/roomService.go +++ b/services/data-manager/service/room/roomService.go @@ -48,7 +48,7 @@ func GetRoomSchedule(app *pocketbase.PocketBase, room string, from string, to st room = functions.MapRoom(room, false) } - roomSchedule, err := db.GetRoomSchedule(app, room, from, to) + roomSchedule, err := db.GetRoomScheduleInTimeSpan(app, room, from, to) if err != nil { return nil, err } diff --git a/services/ical/service/connector/grpc/events.go b/services/ical/service/connector/grpc/events.go index bcb6b1b..b56fc5b 100644 --- a/services/ical/service/connector/grpc/events.go +++ b/services/ical/service/connector/grpc/events.go @@ -25,7 +25,7 @@ import ( "time" ) -func GetEvents(modules []model.FeedModule, conn *grpc.ClientConn) (model.Events, error) { +func GetEventsByModules(modules []model.FeedModule, conn *grpc.ClientConn) (model.Events, error) { c := pb.NewModuleServiceClient(conn) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -48,3 +48,21 @@ func GetEvents(modules []model.FeedModule, conn *grpc.ClientConn) (model.Events, return events, nil } + +func GetEventsByRoom(room string, conn *grpc.ClientConn) (model.Events, error) { + c := pb.NewRoomServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + r, err := c.GetRoomEvents(ctx, &pb.GetRoomRequest{Room: room}) + if err != nil { + slog.Error("could not get room events: ", "error", err) + } + + events := make(model.Events, 0) + for _, event := range r.GetEvents() { + events = append(events, protoToEvent(event)) + } + + return events, nil +} diff --git a/services/ical/service/ical/ical.go b/services/ical/service/ical/ical.go index 95f2272..eaf381a 100644 --- a/services/ical/service/ical/ical.go +++ b/services/ical/service/ical/ical.go @@ -54,7 +54,7 @@ func Feed(app model.AppType, token string, userAgent string) (string, string, er } // Get all events for modules - events, err = htwkalenderGrpc.GetEvents(feed.Modules, app.GrpcClient) + events, err = htwkalenderGrpc.GetEventsByModules(feed.Modules, app.GrpcClient) if err != nil { return "", "", err } @@ -106,3 +106,23 @@ func CreateFeed(app model.AppType, modules []model.FeedCollection) (string, erro return token, nil } + +func FeedRoom(app model.AppType, room string) (string, string, error) { + + // Get all events for room + events, err := htwkalenderGrpc.GetEventsByRoom(room, app.GrpcClient) + if err != nil { + return "", "", err + } + + // Sort events by start date + events.Sort() + + // Generate one Hash for E-TAG from all events + etag := functions.HashString(events.String()) + + cal := GenerateIcalFeed(events, map[string]model.FeedCollection{}, "") + icalFeed := &model.FeedModel{Content: cal.Serialize(), ExpiresAt: model.JSONTime(time.Now().Add(expirationTime))} + + return icalFeed.Content, etag, nil +} diff --git a/services/ical/service/routes.go b/services/ical/service/routes.go index 3174d46..882194b 100644 --- a/services/ical/service/routes.go +++ b/services/ical/service/routes.go @@ -120,6 +120,27 @@ func AddFeedRoutes(app model.AppType) { return c.JSON(http.StatusOK, "token: "+token) }) + app.Fiber.Get("/api/feed/room", func(c fiber.Ctx) error { + room := c.Query("id") + ifNoneMatch := c.Get("If-None-Match") + + results, etag, err := ical.FeedRoom(app, room) + if err != nil { + slog.Error("Failed to get feed", "error", err, "room", room) + return c.SendStatus(fiber.StatusNotFound) + } + + if ifNoneMatch == etag && ifNoneMatch != "" { + return c.SendStatus(fiber.StatusNotModified) + } + + c.Response().Header.Set("Content-type", "text/calendar") + c.Response().Header.Set("charset", "utf-8") + c.Response().Header.Set("Content-Disposition", "inline") + c.Response().Header.Set("filename", "calendar.ics") + return c.SendString(results) + }) + app.Fiber.Head("/api/feed", func(c fiber.Ctx) error { return c.JSON(http.StatusOK, "") }) diff --git a/services/protobuf/room.proto b/services/protobuf/room.proto new file mode 100644 index 0000000..3dfc58f --- /dev/null +++ b/services/protobuf/room.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +import "modules.proto"; + +option go_package = "htwkalender/common/modules"; + +service RoomService { + rpc GetRoomEvents(GetRoomRequest) returns (GetRoomResponse) {} +} + +message GetRoomResponse { + string room = 1; + repeated Event events = 2; +} + +message GetRoomRequest { + string room = 1; +} +