feat:#16 added room ics api route

This commit is contained in:
Elmar Kresse
2024-10-25 00:16:02 +02:00
parent 3d41d9d6a4
commit 42172fb0a5
13 changed files with 505 additions and 6 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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",
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, "")
})

View File

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