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/frontend/src/helpers/url.ts b/frontend/src/helpers/url.ts
new file mode 100644
index 0000000..16c08cb
--- /dev/null
+++ b/frontend/src/helpers/url.ts
@@ -0,0 +1,25 @@
+//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
+//Copyright (C) 2024 HTWKalender support@htwkalender.de
+
+//This program is free software: you can redistribute it and/or modify
+//it under the terms of the GNU Affero General Public License as published by
+//the Free Software Foundation, either version 3 of the License, or
+//(at your option) any later version.
+
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//GNU Affero General Public License for more details.
+
+//You should have received a copy of the GNU Affero General Public License
+//along with this program. If not, see .
+
+import { inject } from "vue";
+
+const domain = import.meta.env.SSR
+ ? inject("domain")!
+ : window.location.hostname;
+
+export function getLink(path: string, selectedRoom: string) {
+ return "https://" + domain + path + selectedRoom;
+}
\ No newline at end of file
diff --git a/frontend/src/i18n/translations/de.json b/frontend/src/i18n/translations/de.json
index 68c5bfe..38da365 100644
--- a/frontend/src/i18n/translations/de.json
+++ b/frontend/src/i18n/translations/de.json
@@ -27,7 +27,8 @@
"dropDownSelect": "Bitte wähle einen Raum aus",
"noRoomsAvailable": "Keine Räume verfügbar",
"available": "verfügbar",
- "occupied": "belegt"
+ "occupied": "belegt",
+ "roomIcal": "iCal für "
},
"freeRooms": {
"freeRooms": "Freie Räume",
diff --git a/frontend/src/i18n/translations/en.json b/frontend/src/i18n/translations/en.json
index bc3c9e6..8e1de02 100644
--- a/frontend/src/i18n/translations/en.json
+++ b/frontend/src/i18n/translations/en.json
@@ -27,7 +27,8 @@
"dropDownSelect": "please select a room",
"noRoomsAvailable": "no rooms listed",
"available": "available",
- "occupied": "occupied"
+ "occupied": "occupied",
+ "roomIcal": "iCal for "
},
"freeRooms": {
"freeRooms": "free rooms",
diff --git a/frontend/src/view/CalendarLink.vue b/frontend/src/view/CalendarLink.vue
index 41f6dc5..d21d2d0 100644
--- a/frontend/src/view/CalendarLink.vue
+++ b/frontend/src/view/CalendarLink.vue
@@ -19,19 +19,14 @@ along with this program. If not, see .
@@ -88,6 +126,7 @@ watch(selectedRoom, (newRoom: Room) => {
:headline="$t('roomFinderPage.headline')"
:sub-title="$t('roomFinderPage.detail')"
icon="pi pi-search"
+ :lower-button="button"
>
{
+
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..e71b6cf
--- /dev/null
+++ b/services/data-manager/service/grpc/roomService.go
@@ -0,0 +1,36 @@
+package grpc
+
+import (
+ "context"
+ "github.com/pocketbase/pocketbase"
+ pb "htwkalender/common/genproto/modules"
+ "htwkalender/data-manager/service/db"
+ "log/slog"
+)
+
+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,
+ )
+
+ slog.Error("GetRoomEvents", "room", 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;
+}
+