mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender.git
synced 2025-07-16 09:38:49 +02:00
feat:#7 added new folder structure and updated api in ical
This commit is contained in:
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -1,199 +0,0 @@
|
||||
//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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"htwkalender/service/db"
|
||||
"htwkalender/service/ical"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func addFeedRoutes(app *pocketbase.PocketBase) {
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodPost,
|
||||
Path: "/api/createFeed",
|
||||
Handler: func(c echo.Context) error {
|
||||
requestBody, _ := io.ReadAll(c.Request().Body)
|
||||
result, err := ical.CreateIndividualFeed(requestBody, app)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, "Failed to create individual feed")
|
||||
}
|
||||
return c.JSON(http.StatusOK, result)
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/api/feed",
|
||||
Handler: func(c echo.Context) error {
|
||||
token := c.QueryParam("token")
|
||||
result, err := ical.Feed(app, token)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, "Failed to get feed")
|
||||
}
|
||||
var responseWriter = c.Response().Writer
|
||||
responseWriter.Header().Set("Content-type", "text/calendar")
|
||||
responseWriter.Header().Set("charset", "utf-8")
|
||||
responseWriter.Header().Set("Content-Disposition", "inline")
|
||||
responseWriter.Header().Set("filename", "calendar.ics")
|
||||
|
||||
responseWriter.WriteHeader(http.StatusOK)
|
||||
_, err = responseWriter.Write([]byte(result))
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, "Failed to write feed")
|
||||
}
|
||||
c.Response().Writer = responseWriter
|
||||
return nil
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodDelete,
|
||||
Path: "/api/feed",
|
||||
Handler: func(c echo.Context) error {
|
||||
token := c.QueryParam("token")
|
||||
err := db.DeleteFeed(app.Dao(), token)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusNotFound, err)
|
||||
} else {
|
||||
return c.JSON(http.StatusOK, "Feed deleted")
|
||||
}
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodPut,
|
||||
Path: "/api/feed",
|
||||
Handler: func(c echo.Context) error {
|
||||
token := c.QueryParam("token")
|
||||
return c.JSON(http.StatusOK, "token: "+token)
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Route for Thunderbird to get calendar server information
|
||||
// Response is a 200 OK without additional content
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: "PROPFIND",
|
||||
Path: "/",
|
||||
Handler: func(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, "")
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Route for Thunderbird to get calendar server information
|
||||
// Response is a 200 OK without additional content
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: "PROPFIND",
|
||||
Path: "/.well-known/caldav",
|
||||
Handler: func(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, "")
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: "PROPFIND",
|
||||
Path: "/api/feed",
|
||||
Handler: func(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, "")
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodHead,
|
||||
Path: "/api/feed",
|
||||
Handler: func(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, "")
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
@ -14,13 +14,11 @@
|
||||
#You should have received a copy of the GNU Affero General Public License
|
||||
#along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
htwkalender-backend:
|
||||
build:
|
||||
dockerfile: Dockerfile
|
||||
context: ./backend
|
||||
context: ./services/backend
|
||||
target: dev # prod
|
||||
command: "--http=0.0.0.0:8090 --dir=/htwkalender/data/pb_data"
|
||||
ports:
|
||||
@ -29,6 +27,13 @@ services:
|
||||
- pb_data:/htwkalender/data # for production with volume
|
||||
# - ./backend:/htwkalender/data # for development with bind mount from project directory
|
||||
|
||||
htwkalender-ical:
|
||||
build:
|
||||
dockerfile: Dockerfile
|
||||
context: ./services/ical
|
||||
target: dev # prod
|
||||
|
||||
|
||||
htwkalender-frontend:
|
||||
build:
|
||||
dockerfile: Dockerfile
|
||||
|
@ -18,7 +18,7 @@ import { Module } from "../model/module.ts";
|
||||
|
||||
export async function createIndividualFeed(modules: Module[]): Promise<string> {
|
||||
try {
|
||||
const response = await fetch("/api/createFeed", {
|
||||
const response = await fetch("/api/feed", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
@ -52,6 +52,15 @@ http {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
location /api/feed {
|
||||
proxy_pass http://htwkalender-ical:8091;
|
||||
client_max_body_size 20m;
|
||||
proxy_connect_timeout 600s;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
send_timeout 600s;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://htwkalender-backend:8090;
|
||||
client_max_body_size 20m;
|
||||
|
76
services/backend/service/addCalDavRoutes.go
Normal file
76
services/backend/service/addCalDavRoutes.go
Normal file
@ -0,0 +1,76 @@
|
||||
//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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"htwkalender/service/db"
|
||||
"htwkalender/service/ical"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func addFeedRoutes(app *pocketbase.PocketBase) {
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodPost,
|
||||
Path: "/api/feed",
|
||||
Handler: func(c echo.Context) error {
|
||||
requestBody, _ := io.ReadAll(c.Request().Body)
|
||||
result, err := ical.CreateIndividualFeed(requestBody, app)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, "Failed to create individual feed")
|
||||
}
|
||||
return c.JSON(http.StatusOK, result)
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
_, err := e.Router.AddRoute(echo.Route{
|
||||
Method: http.MethodDelete,
|
||||
Path: "/api/feed",
|
||||
Handler: func(c echo.Context) error {
|
||||
token := c.QueryParam("token")
|
||||
err := db.DeleteFeed(app.Dao(), token)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusNotFound, err)
|
||||
} else {
|
||||
return c.JSON(http.StatusOK, "Feed deleted")
|
||||
}
|
||||
},
|
||||
Middlewares: []echo.MiddlewareFunc{
|
||||
apis.ActivityLogger(app),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
@ -17,60 +17,13 @@
|
||||
package ical
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"htwkalender/model"
|
||||
"htwkalender/service/db"
|
||||
"htwkalender/service/feed"
|
||||
"time"
|
||||
|
||||
"github.com/jordic/goics"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"htwkalender/model"
|
||||
"htwkalender/service/db"
|
||||
)
|
||||
|
||||
const expirationTime = 5 * time.Minute
|
||||
|
||||
func Feed(app *pocketbase.PocketBase, token string) (string, error) {
|
||||
var result string
|
||||
|
||||
icalFeed, err := db.FindFeedByToken(token, app)
|
||||
if icalFeed == nil && err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
modules := make(map[string]model.FeedCollection)
|
||||
var modulesArray []model.FeedCollection
|
||||
_ = json.Unmarshal([]byte(icalFeed.Modules), &modulesArray)
|
||||
|
||||
for _, module := range modulesArray {
|
||||
modules[module.UUID] = module
|
||||
}
|
||||
|
||||
newFeed, createFeedErr := createFeedForToken(app, modules)
|
||||
if createFeedErr != nil {
|
||||
return "", createFeedErr
|
||||
}
|
||||
result = newFeed.Content
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func createFeedForToken(app *pocketbase.PocketBase, modules map[string]model.FeedCollection) (*model.FeedModel, error) {
|
||||
events, err := db.GetPlanForModules(app, modules)
|
||||
if err != nil {
|
||||
return nil, apis.NewNotFoundError("Could not fetch events", err)
|
||||
}
|
||||
|
||||
// Combine Events
|
||||
events = feed.CombineEventsInFeed(events)
|
||||
|
||||
b := bytes.Buffer{}
|
||||
goics.NewICalEncode(&b).Encode(IcalModel{Events: events, Mapping: modules})
|
||||
icalFeed := &model.FeedModel{Content: b.String(), ExpiresAt: time.Now().Add(expirationTime)}
|
||||
return icalFeed, nil
|
||||
}
|
||||
|
||||
func CreateIndividualFeed(requestBody []byte, app *pocketbase.PocketBase) (string, error) {
|
||||
var modules []model.FeedCollection
|
||||
|
70
services/ical/Dockerfile
Normal file
70
services/ical/Dockerfile
Normal file
@ -0,0 +1,70 @@
|
||||
#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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
# build stage
|
||||
FROM golang:alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the source from the current directory to the Working Directory inside the container
|
||||
COPY . ./
|
||||
# download needed modules
|
||||
RUN apk add --no-cache --update go gcc g++ && \
|
||||
go mod download && \
|
||||
CGO_ENABLED=1 GOOS=linux go build -o /htwkalender-ical
|
||||
|
||||
# production stage
|
||||
FROM alpine:latest AS prod
|
||||
|
||||
WORKDIR /htwkalender-ical
|
||||
|
||||
ARG USER=ical
|
||||
RUN adduser -Ds /bin/sh $USER && \
|
||||
chown $USER:$USER ./
|
||||
|
||||
USER $USER
|
||||
RUN mkdir -p data
|
||||
|
||||
# copies executable from build container
|
||||
COPY --chown=$USER:$USER --from=build /htwkalender-ical ./
|
||||
|
||||
# Expose port 8090 to the outside world
|
||||
EXPOSE 8091
|
||||
|
||||
ENTRYPOINT ["./htwkalender-ical"]
|
||||
|
||||
|
||||
FROM golang:1.21.6 AS dev
|
||||
|
||||
# Set the Current Working Directory inside the container
|
||||
WORKDIR /htwkalender-ical
|
||||
|
||||
# Copy go mod and sum files
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy the source from the current directory to the Working Directory inside the container
|
||||
COPY *.go ./
|
||||
COPY . .
|
||||
|
||||
# Build the Go app
|
||||
RUN CGO_ENABLED=1 GOOS=linux go build -o /htwkalender-ical
|
||||
|
||||
# Expose port 8090 to the outside world
|
||||
EXPOSE 8090
|
||||
|
||||
# Entry point
|
||||
ENTRYPOINT ["./htwkalender-ical"]
|
@ -22,16 +22,24 @@ import (
|
||||
"htwkalender-ical/service"
|
||||
)
|
||||
|
||||
// main function for the backend-ical service
|
||||
// main function for the ical service
|
||||
// uses rest api to get the data from the backend
|
||||
// exposes rest api endpoints with fiber to serve the data for clients
|
||||
func main() {
|
||||
|
||||
// Initialize a new Fiber app
|
||||
app := fiber.New()
|
||||
webdavRequestMethods := []string{"PROPFIND", "MKCOL", "COPY", "MOVE"}
|
||||
|
||||
app := fiber.New(fiber.Config{
|
||||
CaseSensitive: true,
|
||||
StrictRouting: true,
|
||||
ServerHeader: "Fiber",
|
||||
AppName: "App Name",
|
||||
RequestMethods: append(fiber.DefaultMethods[:], webdavRequestMethods...),
|
||||
})
|
||||
|
||||
// Add routes to the app instance for the backend ical service
|
||||
service.AddFeedRoutes(app)
|
||||
|
||||
log.Fatal(app.Listen(":3000"))
|
||||
log.Fatal(app.Listen(":8091"))
|
||||
}
|
@ -38,6 +38,15 @@ func parseResponse(response []byte) (model.FeedRecord, error) {
|
||||
return feedRecord, nil
|
||||
}
|
||||
|
||||
func DeleteFeedRecord(token string) error {
|
||||
err := DeleteRequestApi("/api/feed?token=" + token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetModuleWithEvents(module model.FeedModule) (model.Module, error) {
|
||||
var modules model.Module
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
|
||||
func RequestApi(path string) (*client.Response, error) {
|
||||
|
||||
var host = "http://localhost"
|
||||
var host = "http://htwkalender-backend:8090"
|
||||
|
||||
cc := client.New()
|
||||
cc.SetTimeout(5 * time.Second)
|
||||
@ -20,3 +20,19 @@ func RequestApi(path string) (*client.Response, error) {
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func DeleteRequestApi(path string) error {
|
||||
|
||||
var host = "http://htwkalender-backend:8090"
|
||||
|
||||
cc := client.New()
|
||||
cc.SetTimeout(5 * time.Second)
|
||||
|
||||
// set retry to 0
|
||||
_, err := cc.Delete(host + path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user