Commit: Bulk unfinished work

This commit is contained in:
2026-01-22 17:39:04 +01:00
parent 6c46b4efcc
commit 3a9acc42a2
68 changed files with 5047 additions and 1064 deletions

View File

@@ -1,128 +0,0 @@
package routers
import (
"git.kocoder.xyz/kocoded/vt/model"
"git.kocoder.xyz/kocoded/vt/query"
"git.kocoder.xyz/kocoded/vt/utils"
"github.com/gofiber/fiber/v2"
)
type ansprechpartnerRouter struct {
utils.Application
}
func RegisterAnsprechpartnerRouter(group fiber.Router, appCtx utils.Application) {
router := &ansprechpartnerRouter{Application: appCtx}
r := group.Use(utils.IsAuthenticated(appCtx))
r.Post("/new", router.createAnsprechpartner)
r.Get("/all", router.getAllAnsprechpartners)
r.Get("/:id<int>", router.getAnsprechpartner)
r.Get("/:id<int>/firmen", router.getAnsprechpartnerFirmen)
r.Put("/:id<int>", router.updateAnsprechpartner)
r.Delete("/:id<int>", router.deleteAnsprechpartner)
}
func (r *ansprechpartnerRouter) createAnsprechpartner(c *fiber.Ctx) error {
ansprechpartner := new(model.Ansprechpartner)
if err := c.BodyParser(ansprechpartner); err != nil {
return err
}
ap := query.Ansprechpartner
if err := ap.Omit(ap.UpdatedAt, ap.DeletedAt).Create(ansprechpartner); err != nil {
return err
}
return c.SendString("Hello")
}
func (r *ansprechpartnerRouter) getAllAnsprechpartners(c *fiber.Ctx) error {
aps, err := query.Ansprechpartner.Find()
if err != nil {
return err
}
err = c.JSON(aps)
if err != nil {
return err
}
return nil
}
func (r *ansprechpartnerRouter) getAnsprechpartner(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return err
}
ap, err := query.Ansprechpartner.Where(query.Ansprechpartner.ID.Eq(uint(id))).First()
if err != nil {
return err
}
return c.JSON(ap)
}
func (r *ansprechpartnerRouter) getAnsprechpartnerFirmen(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return err
}
ap, err := query.Ansprechpartner.Where(query.Ansprechpartner.ID.Eq(uint(id))).First()
if err != nil {
return err
}
firmen, err := query.Ansprechpartner.Firmen.Model(ap).Find()
if err != nil {
return err
}
ap.Firmen = firmen
return c.JSON(ap)
}
func (r *ansprechpartnerRouter) updateAnsprechpartner(c *fiber.Ctx) error {
ansprechpartner := new(model.Ansprechpartner)
id, err := c.ParamsInt("id")
if err != nil {
return err
}
if err = c.BodyParser(ansprechpartner); err != nil {
return err
}
ap := query.Ansprechpartner
res, err := ap.Where(ap.ID.Eq(uint(id))).Omit(ap.ID, ap.CreatedAt, ap.UpdatedAt, ap.DeletedAt).Updates(ansprechpartner)
if err != nil {
return err
}
return c.JSON(res)
}
func (r *ansprechpartnerRouter) deleteAnsprechpartner(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return err
}
res, err := query.Ansprechpartner.Where(query.Ansprechpartner.ID.Value(uint(id))).Delete()
if err != nil {
return err
}
return c.JSON(res)
}

View File

@@ -1,105 +0,0 @@
package routers
import (
"git.kocoder.xyz/kocoded/vt/model"
"git.kocoder.xyz/kocoded/vt/query"
"git.kocoder.xyz/kocoded/vt/utils"
"github.com/gofiber/fiber/v2"
)
type firmaRouter struct {
utils.Application
}
func RegisterFirmaRouter(group fiber.Router, appCtx utils.Application) {
router := &firmaRouter{Application: appCtx}
r := group.Use(utils.IsAuthenticated(appCtx))
r.Post("/new", router.createFirma)
r.Get("/all", router.getAllFirmen)
r.Get("/:id<int>", router.getFirma)
r.Put("/:id<int>", router.updateFirma)
r.Delete("/:id<int>", router.deleteFirma)
}
func (r *firmaRouter) createFirma(c *fiber.Ctx) error {
firma := new(model.Firma)
if err := c.BodyParser(firma); err != nil {
return err
}
ap := query.Firma
if err := ap.Omit(ap.UpdatedAt, ap.DeletedAt).Create(firma); err != nil {
return err
}
return c.SendString("Hello")
}
func (r *firmaRouter) getAllFirmen(c *fiber.Ctx) error {
aps, err := query.Firma.Find()
if err != nil {
return err
}
err = c.JSON(aps)
if err != nil {
return err
}
return nil
}
func (r *firmaRouter) getFirma(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return err
}
ap, err := query.Firma.Where(query.Firma.ID.Eq(uint(id))).First()
if err != nil {
return err
}
return c.JSON(ap)
}
func (r *firmaRouter) updateFirma(c *fiber.Ctx) error {
firma := new(model.Firma)
id, err := c.ParamsInt("id")
if err != nil {
return err
}
if err = c.BodyParser(firma); err != nil {
return err
}
ap := query.Firma
res, err := ap.Where(ap.ID.Eq(uint(id))).Omit(ap.ID, ap.CreatedAt, ap.UpdatedAt, ap.DeletedAt).Updates(firma)
if err != nil {
return err
}
return c.JSON(res)
}
func (r *firmaRouter) deleteFirma(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return err
}
res, err := query.Firma.Where(query.Firma.ID.Value(uint(id))).Delete()
if err != nil {
return err
}
return c.JSON(res)
}

View File

@@ -1,65 +0,0 @@
package routers
import (
"git.kocoder.xyz/kocoded/vt/model"
"git.kocoder.xyz/kocoded/vt/query"
"git.kocoder.xyz/kocoded/vt/utils"
"github.com/gofiber/fiber/v2"
)
type mandantRouter struct {
utils.Application
currentMandant uint
}
func RegisterMandantRouter(group fiber.Router, appCtx utils.Application) {
router := &mandantRouter{currentMandant: 1, Application: appCtx}
r := group.Use(utils.IsAuthenticated(appCtx))
r.Get("/current", router.getCurrentMandant)
r.Put("/current", router.setCurrentMandant)
r.Get("/all", router.getAllMandant)
}
func (r *mandantRouter) getCurrentMandant(c *fiber.Ctx) error {
m := query.Mandant
currentMandant, err := m.Where(m.ID.Eq(r.currentMandant)).First()
if err != nil {
r.Logger.Warn("Current mandant not found.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(currentMandant)
}
func (r *mandantRouter) getAllMandant(c *fiber.Ctx) error {
m := query.Mandant
mandanten, err := m.Find()
if err != nil {
r.Logger.Warn("Current mandant not found.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(mandanten)
}
func (r *mandantRouter) setCurrentMandant(c *fiber.Ctx) error {
m := query.Mandant
mandant := &model.Mandant{}
if err := c.BodyParser(mandant); err != nil {
return err
}
r.currentMandant = mandant.ID
currentMandant, err := m.Where(m.ID.Eq(r.currentMandant)).First()
if err != nil {
r.Logger.Warn("Current mandant not found.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(currentMandant)
}

View File

@@ -0,0 +1,96 @@
package mandantv1
import (
"context"
"errors"
"log/slog"
"connectrpc.com/connect"
"git.kocoder.xyz/kocoded/vt/fx"
"git.kocoder.xyz/kocoded/vt/fx/interfaces/stores"
mandantv1 "git.kocoder.xyz/kocoded/vt/gen/mandant/v1"
"git.kocoder.xyz/kocoded/vt/gen/mandant/v1/mandantv1connect"
"git.kocoder.xyz/kocoded/vt/interceptors"
"git.kocoder.xyz/kocoded/vt/query"
"gorm.io/gorm"
)
func NewMandantRoute(logger *slog.Logger, db *gorm.DB, sr stores.SessionStore, oidcInterceptor *interceptors.AuthenticationInterceptor) fx.Handler {
path, handler := mandantv1connect.NewMandantServiceHandler(&mandantService{logger: logger, db: db, sr: sr}, connect.WithInterceptors(oidcInterceptor))
return fx.NewRoute(path, handler)
}
type mandantService struct {
logger *slog.Logger
db *gorm.DB
sr stores.SessionStore
}
func (m *mandantService) GetAllTenants(ctx context.Context, req *mandantv1.ListTenantRequest) (*mandantv1.ListProjectsResponse, error) {
ma := query.Use(m.db).Mandant
tenants, err := ma.Find()
if err != nil {
m.logger.Error("Error fetching tenants", "error", err)
return nil, connect.NewError(connect.CodeUnknown, err)
}
tenantResponses := make([]*mandantv1.GetTenantResponse, len(tenants))
for i, t := range tenants {
tenantResponses[i] = &mandantv1.GetTenantResponse{
Id: int64(t.ID),
Name: t.Name,
Plan: t.Plan,
Logo: t.Logo,
Color: t.Color,
}
}
return &mandantv1.ListProjectsResponse{
Data: tenantResponses,
Meta: &mandantv1.Metadata{
TotalCount: int32(len(tenants)),
},
}, nil
}
func (m *mandantService) GetCurrentTenant(ctx context.Context, req *mandantv1.GetCurrentTenantRequest) (*mandantv1.GetTenantResponse, error) {
s, ok := interceptors.SessionFromContext(ctx)
if !ok {
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set."))
}
ma := query.Use(m.db).Mandant
res, err := ma.Where(ma.ID.Eq(s.MandantId)).First()
if err != nil {
m.logger.Error("Error fetching current tenant", "error", err)
return nil, connect.NewError(connect.CodeUnknown, err)
}
return &mandantv1.GetTenantResponse{
Id: int64(res.ID),
Name: res.Name,
Plan: res.Plan,
Logo: res.Logo,
Color: res.Color,
}, nil
}
func (m *mandantService) SetCurrentTenant(ctx context.Context, req *mandantv1.SetCurrentTenantRequest) (*mandantv1.SetCurrentTenantResponse, error) {
s, ok := interceptors.SessionFromContext(ctx)
if !ok {
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set."))
}
err := m.sr.SetMandantInSession(s.Token, uint(req.TenantId))
if err != nil {
m.logger.Error("Error setting mandant in session", "error", err)
return nil, connect.NewError(connect.CodeUnknown, err)
}
return &mandantv1.SetCurrentTenantResponse{
Success: true,
}, nil
}

View File

@@ -0,0 +1,48 @@
syntax = "proto3";
package mandant.v1;
message GetCurrentTenantRequest {
}
message GetTenantRequest {
int64 id = 1;
}
message GetTenantResponse {
int64 id = 1;
string name = 2;
string plan = 3;
string logo = 4;
string color = 5;
}
message ListTenantRequest {
int32 page = 1;
int32 per_page = 2;
string orber_by = 3;
bool asc = 4;
}
message Metadata {
int32 totalCount = 1;
}
message ListProjectsResponse {
repeated GetTenantResponse data = 1;
Metadata meta = 2;
}
message SetCurrentTenantRequest {
int64 tenant_id = 1;
}
message SetCurrentTenantResponse {
bool success = 1;
}
service MandantService {
rpc GetCurrentTenant(GetCurrentTenantRequest) returns (GetTenantResponse);
rpc GetAllTenants(ListTenantRequest) returns (ListProjectsResponse);
rpc SetCurrentTenant(SetCurrentTenantRequest) returns (SetCurrentTenantResponse);
}

View File

@@ -0,0 +1,48 @@
syntax = "proto3";
package mandant.v1;
message GetCurrentTenantRequest {
}
message GetTenantRequest {
int64 id = 1;
}
message GetTenantResponse {
int64 id = 1;
string name = 2;
string plan = 3;
string logo = 4;
string color = 5;
}
message ListTenantRequest {
int32 page = 1;
int32 per_page = 2;
string orber_by = 3;
bool asc = 4;
}
message Metadata {
int32 totalCount = 1;
}
message ListProjectsResponse {
repeated GetTenantResponse data = 1;
Metadata meta = 2;
}
message SetCurrentTenantRequest {
int64 tenant_id = 1;
}
message SetCurrentTenantResponse {
bool success = 1;
}
service MandantService {
rpc GetCurrentTenant(GetCurrentTenantRequest) returns (GetTenantResponse);
rpc GetAllTenants(ListTenantRequest) returns (ListProjectsResponse);
rpc SetCurrentTenant(SetCurrentTenantRequest) returns (SetCurrentTenantResponse);
}

View File

@@ -0,0 +1,30 @@
package messagebusv1
import (
"context"
"time"
"connectrpc.com/connect"
kfx "git.kocoder.xyz/kocoded/vt/fx"
messagebusv1 "git.kocoder.xyz/kocoded/vt/gen/messagebus/v1"
"git.kocoder.xyz/kocoded/vt/gen/messagebus/v1/messagebusv1connect"
)
func NewMessagebusRoute() kfx.Handler {
path, handler := messagebusv1connect.NewMessageBusServiceHandler(&messagebusService{})
return kfx.NewRoute(path, handler)
}
type messagebusService struct {
}
func (mbs *messagebusService) SubscribeToConnectInvalidationRequests(ctx context.Context, req *messagebusv1.SubscribeToConnectInvalidationRequestsRequest, res *connect.ServerStream[messagebusv1.MessageBusEntity]) error {
for {
err := res.Send(&messagebusv1.MessageBusEntity{QueryKey: "Hello World!"})
if err != nil {
panic(err)
}
time.Sleep(time.Second * 2)
}
}

View File

@@ -0,0 +1,18 @@
syntax = "proto3";
package messagebus.v1;
enum MessageBusEntityType {
OTHER = 0;
INVALIDATION_REQUEST = 1;
}
message MessageBusEntity {
string queryKey = 1;
}
message SubscribeToConnectInvalidationRequestsRequest {}
service MessageBusService {
rpc SubscribeToConnectInvalidationRequests(SubscribeToConnectInvalidationRequestsRequest) returns (stream MessageBusEntity);
}

View File

@@ -1,171 +0,0 @@
package routers
import (
"strconv"
"git.kocoder.xyz/kocoded/vt/model"
"git.kocoder.xyz/kocoded/vt/query"
"git.kocoder.xyz/kocoded/vt/utils"
"github.com/gofiber/fiber/v2"
)
type projectRouter struct {
utils.Application
currentMandant uint
}
func RegisterProjectRouter(group fiber.Router, appCtx utils.Application) {
router := &projectRouter{currentMandant: 1, Application: appCtx}
r := group.Use(utils.IsAuthenticated(appCtx))
r.Get("/all", router.getAllProjects)
r.Post("/new", router.createNewProject)
r.Get("/:id<int>", router.getProject)
r.Post("/:id<int>/edit", router.editProject)
r.Delete("/:id<int>/delete", router.deleteProject)
}
func (r *projectRouter) getAllProjects(c *fiber.Ctx) error {
p := query.Projekt
pph := c.Get("X-PER-PAGE")
ofh := c.Get("X-OFFSET")
params := struct {
Id string `params:"id"`
Desc bool `params:"desc"`
}{}
if err := c.QueryParser(&params); err != nil {
r.Logger.Warn("Param Parser Error: ", "err", err)
}
var pp, of int
pp, err := strconv.Atoi(pph)
if err != nil {
r.Logger.Warn("Per Page header not found.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
of, err = strconv.Atoi(ofh)
if err != nil {
r.Logger.Warn("Offset header not found.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
query := p.Where().Limit(pp).Offset(of)
if params.Id != "" {
f, ok := p.GetFieldByName(params.Id)
if ok {
if params.Desc {
query = query.Order(f.Desc())
} else {
query = query.Order(f.Asc())
}
}
} else {
query = query.Order(p.ID.Asc())
}
projects, err := query.Order(p.Name.Asc()).Find()
if err != nil {
r.Logger.Warn("Current mandant not found.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
cnt, err := p.Count()
if err != nil {
r.Logger.Warn("Current mandant not found.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
res := &PaginatedProjects{Data: projects, Meta: struct {
TotalProjectsCount int64 `json:"totalProjectsCount"`
}{TotalProjectsCount: cnt}}
return c.JSON(res)
}
type PaginatedProjects struct {
Data []*model.Projekt `json:"data"`
Meta struct {
TotalProjectsCount int64 `json:"totalProjectsCount"`
} `json:"meta"`
}
func (r *projectRouter) getProject(c *fiber.Ctx) error {
ids := c.Params("id")
id, err := strconv.ParseUint(ids, 10, 32)
if err != nil {
r.Logger.Warn("Id is not an int.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
p := query.Projekt
currentProject, err := p.Where(p.ID.Eq(uint(id))).First()
if err != nil {
r.Logger.Warn("Current mandant not found.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(currentProject)
}
func (r *projectRouter) createNewProject(c *fiber.Ctx) error {
p := query.Projekt
project := &model.Projekt{}
if err := c.BodyParser(project); err != nil {
return err
}
err := p.Create(project)
if err != nil {
r.Logger.Warn("Couldn't create Projejct.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
utils.MessageBus.SendMBObject(int(project.MandantID), utils.MessageBusObject{Entity: []string{"projects", "list"}})
utils.MessageBus.SendMBObject(int(project.MandantID), utils.MessageBusObject{Entity: []string{"projects", "get"}, Id: int(project.ID)})
return c.JSON(project)
}
func (r *projectRouter) editProject(c *fiber.Ctx) error {
p := query.Projekt
project := &model.Projekt{}
if err := c.BodyParser(project); err != nil {
return err
}
res, err := p.Where(p.ID.Eq(project.ID)).Updates(project)
if err != nil {
r.Logger.Warn("Couldn't create Projejct.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
utils.MessageBus.SendMBObject(int(project.MandantID), utils.MessageBusObject{Entity: []string{"projects", "list"}})
utils.MessageBus.SendMBObject(int(project.MandantID), utils.MessageBusObject{Entity: []string{"projects", "get"}, Id: int(project.ID)})
return c.JSON(res)
}
func (r *projectRouter) deleteProject(c *fiber.Ctx) error {
ids := c.Params("id")
id, err := strconv.ParseUint(ids, 10, 32)
if err != nil {
r.Logger.Warn("Id is not an int.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
p := query.Projekt
res, err := p.Where(p.ID.Eq(uint(id))).Delete()
if err != nil {
r.Logger.Warn("Couldn't create Projejct.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(res)
}

View File

@@ -0,0 +1,125 @@
package projectv1
import (
"context"
"errors"
"slices"
"connectrpc.com/connect"
"git.kocoder.xyz/kocoded/vt/fx"
projectv1 "git.kocoder.xyz/kocoded/vt/gen/project/v1"
"git.kocoder.xyz/kocoded/vt/gen/project/v1/projectv1connect"
"git.kocoder.xyz/kocoded/vt/interceptors"
"git.kocoder.xyz/kocoded/vt/model"
"git.kocoder.xyz/kocoded/vt/query"
"gorm.io/gorm"
)
func NewProjectRoute(db *gorm.DB, oidcInterceptor *interceptors.AuthenticationInterceptor) fx.Handler {
path, handler := projectv1connect.NewProjectServiceHandler(&projectService{db: db}, connect.WithInterceptors(oidcInterceptor))
return fx.NewRoute(path, handler)
}
type projectService struct{ db *gorm.DB }
// ListProjects implements projectv1connect.ProjectServiceHandler.
func (s *projectService) ListProjects(ctx context.Context, req *projectv1.ListProjectsRequest) (*projectv1.ListProjectsResponse, error) {
session, ok := interceptors.SessionFromContext(ctx)
if !ok {
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set in interceptor"))
}
p := query.Use(s.db).Projekt
query := p.Where(p.MandantID.Eq(session.MandantId)).Limit(int(req.PerPage)).Offset(int(req.Page))
if req.OrberBy != "" {
f, ok := p.GetFieldByName(req.OrberBy)
if ok {
if !req.Asc {
query = query.Order(f.Desc())
} else {
query = query.Order(f.Asc())
}
}
} else {
query = query.Order(p.ID.Asc())
}
projects, err := query.Find()
if err != nil {
return nil, connect.NewError(connect.CodeUnknown, err)
}
cnt, err := p.Where(p.MandantID.Eq(session.MandantId)).Count()
if err != nil {
return nil, connect.NewError(connect.CodeUnimplemented, err)
}
data := make([]*projectv1.GetProjectResponse, len(projects))
for i, p := range projects {
data[i] = &projectv1.GetProjectResponse{
Id: int64(p.ID),
Name: p.Name,
Description: p.Description,
IsMaterialized: newPointerBoolean(true),
IsPersonalized: newPointerBoolean(true),
IsConfirmed: newPointerBoolean(true),
IsPaid: nil,
IsDone: s.IsDone(p),
}
}
return &projectv1.ListProjectsResponse{
Data: data,
Meta: &projectv1.Metadata{TotalCount: int32(cnt)},
}, nil
}
func newPointerBoolean(val bool) *bool {
b := val
return &b
}
func (s *projectService) IsDone(p *model.Projekt) *bool {
t := query.Use(s.db).Task
tasks, err := t.Where(t.ProjektID.Eq(p.ID)).Find()
if err != nil {
panic(err)
}
if len(tasks) == 0 {
return nil
}
if i := slices.IndexFunc(tasks, func(t *model.Task) bool {
return !t.Status.IsDone()
}); i != -1 {
return newPointerBoolean(false)
}
return newPointerBoolean(true)
}
func (s *projectService) GetProject(ctx context.Context, req *projectv1.GetProjectRequest) (*projectv1.GetProjectResponse, error) {
session, ok := interceptors.SessionFromContext(ctx)
if !ok {
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set in interceptor"))
}
p := query.Use(s.db).Projekt
query, err := p.Where(p.ID.Eq(uint(req.Id))).Where(p.MandantID.Eq(session.MandantId)).First()
if err != nil {
return nil, err
}
return &projectv1.GetProjectResponse{
Id: int64(query.ID),
Name: query.Name,
Description: query.Description,
IsMaterialized: newPointerBoolean(true),
IsPersonalized: newPointerBoolean(true),
IsConfirmed: newPointerBoolean(true),
IsPaid: newPointerBoolean(true),
IsDone: newPointerBoolean(true),
}, nil
}

View File

@@ -0,0 +1,39 @@
syntax = "proto3";
package project.v1;
message GetProjectRequest {
int32 id = 1;
}
message GetProjectResponse {
int64 id = 1;
string name = 2;
string description = 3;
optional bool is_materialized = 5;
optional bool is_personalized = 6;
optional bool is_confirmed = 7;
optional bool is_paid = 8;
optional bool is_done = 9;
}
message ListProjectsRequest {
int32 page = 1;
int32 per_page = 2;
string orber_by = 3;
bool asc = 4;
}
message Metadata {
int32 totalCount = 1;
}
message ListProjectsResponse {
repeated GetProjectResponse data = 1;
Metadata meta = 2;
}
service ProjectService {
rpc GetProject(GetProjectRequest) returns (GetProjectResponse);
rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse);
}

182
routers/todo/v1/todo.go Normal file
View File

@@ -0,0 +1,182 @@
package todov1
import (
"context"
"errors"
"log/slog"
"strconv"
"connectrpc.com/connect"
"git.kocoder.xyz/kocoded/vt/fx"
todov1 "git.kocoder.xyz/kocoded/vt/gen/todo/v1"
"git.kocoder.xyz/kocoded/vt/gen/todo/v1/todov1connect"
"git.kocoder.xyz/kocoded/vt/interceptors"
"git.kocoder.xyz/kocoded/vt/query"
"gorm.io/gen/field"
"gorm.io/gorm"
)
func NewTodoRoute(db *gorm.DB, logger *slog.Logger, oidcInterceptor *interceptors.AuthenticationInterceptor) fx.Handler {
path, handler := todov1connect.NewTodoServiceHandler(&todoService{logger: logger, db: db}, connect.WithInterceptors(oidcInterceptor))
return fx.NewRoute(path, handler)
}
type todoService struct {
logger *slog.Logger
db *gorm.DB
}
func (ts *todoService) GetTodo(ctx context.Context, req *todov1.GetTodosRequest) (*todov1.GetTodosResponse, error) {
s, ok := interceptors.SessionFromContext(ctx)
if !ok {
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set."))
}
t := query.Use(ts.db).Task
res, err := t.Where(t.ID.Eq(uint(req.Id))).Where(t.MandantID.Eq(s.MandantId)).First()
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
tres := &todov1.GetTodosResponse{
Id: int64(res.ID),
Title: res.Titel,
Description: res.Description,
Status: todov1.Status(res.Status),
}
return tres, nil
// return &todov1.GetTodosResponse{
// Id: 1,
// Title: "Sample Todo",
// Description: "This is a sample todo item.",
// Status: todov1.Status_Doing,
// }, nil
}
func (ts *todoService) ContributeToQuery(f *todov1.Filter, q query.ITaskDo) query.ITaskDo {
t := query.Use(ts.db).Task
switch f.Field {
case todov1.Field_FieldDescription, todov1.Field_FieldTitle:
var field field.String
if f.Field == todov1.Field_FieldDescription {
field = t.Description
} else {
field = t.Titel
}
switch f.Operation {
case todov1.Operation_Equals:
q = q.Where(field.Eq(f.Value))
case todov1.Operation_NotEquals:
q = q.Where(field.Neq(f.Value))
case todov1.Operation_GreaterThan:
q = q.Where(field.Gt(f.Value))
case todov1.Operation_LessThan:
q = q.Where(field.Lt(f.Value))
case todov1.Operation_Like:
q = q.Where(field.Like(f.Value))
}
case todov1.Field_FieldId:
i, err := strconv.Atoi(f.Value)
if err != nil {
slog.Warn("Value not an int", "err", err)
return q
}
switch f.Operation {
case todov1.Operation_Equals:
q = q.Where(t.ID.Eq(uint(i)))
case todov1.Operation_NotEquals:
q = q.Where(t.ID.Neq(uint(i)))
case todov1.Operation_GreaterThan:
q = q.Where(t.ID.Gt(uint(i)))
case todov1.Operation_LessThan:
q = q.Where(t.ID.Lt(uint(i)))
}
case todov1.Field_FieldStatus:
i, err := strconv.Atoi(f.Value)
if err != nil {
slog.Warn("Value not an int", "err", err)
return q
}
switch f.Operation {
case todov1.Operation_Equals:
q = q.Where(t.Status.Eq(i))
case todov1.Operation_NotEquals:
q = q.Where(t.Status.Neq(i))
case todov1.Operation_GreaterThan:
q = q.Where(t.Status.Gt(i))
case todov1.Operation_LessThan:
q = q.Where(t.Status.Lt(i))
}
default:
return q
}
return q
}
func (ts *todoService) ListTodos(ctx context.Context, req *todov1.ListTodosRequest) (*todov1.ListTodosResponse, error) {
ts.logger.Info("ListTodos called", "request", req)
s, ok := interceptors.SessionFromContext(ctx)
if !ok {
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set."))
}
t := query.Use(ts.db).Task
taskQuery := t.Where(t.MandantID.Eq(s.MandantId))
for _, v := range req.Filters {
taskQuery = ts.ContributeToQuery(v, taskQuery)
}
taskQuery = taskQuery.Limit(int(req.PerPage)).Offset(int(req.Page))
if req.OrberBy != "" {
f, ok := t.GetFieldByName(req.OrberBy)
if ok {
if !req.Asc {
taskQuery = taskQuery.Order(f.Desc())
} else {
taskQuery = taskQuery.Order(f.Asc())
}
}
} else {
taskQuery = taskQuery.Order(t.ID.Asc())
}
tasks, err := taskQuery.Find()
if err != nil {
return nil, err
}
taskQuery = t.Where(t.MandantID.Eq(s.MandantId))
for _, v := range req.Filters {
taskQuery = ts.ContributeToQuery(v, taskQuery)
}
cnt, err := taskQuery.Count()
if err != nil {
ts.logger.Info("Counting Todos failed", "request", req, "err", err)
panic(err)
}
data := make([]*todov1.GetTodosResponse, len(tasks))
for i, p := range tasks {
data[i] = &todov1.GetTodosResponse{
Id: int64(p.ID),
Title: p.Titel,
Description: p.Description,
Status: todov1.Status(p.Status),
}
}
return &todov1.ListTodosResponse{
Data: data,
Meta: &todov1.Metadata{
TotalCount: int32(cnt),
},
}, nil
}

View File

@@ -0,0 +1,64 @@
syntax = "proto3";
package todo.v1;
enum Status {
Todo = 0;
NeedsMoreInfo = 1;
Doing = 2;
Done = 3;
}
enum Field {
FieldId = 0;
FieldTitle = 1;
FieldDescription = 2;
FieldStatus = 3;
}
enum Operation {
Equals = 0;
NotEquals = 1;
GreaterThan = 2;
LessThan = 3;
Like = 4;
}
message GetTodosRequest {
int32 id = 1;
}
message GetTodosResponse {
int64 id = 1;
string title = 2;
string description = 3;
Status status = 4;
}
message ListTodosRequest {
int32 page = 1;
int32 per_page = 2;
string orber_by = 3;
bool asc = 4;
repeated Filter filters = 5;
}
message Filter {
Field field = 1;
string value = 2;
Operation operation = 3;
}
message Metadata {
int32 totalCount = 1;
}
message ListTodosResponse {
repeated GetTodosResponse data = 1;
Metadata meta = 2;
}
service TodoService {
rpc GetTodo(GetTodosRequest) returns (GetTodosResponse);
rpc ListTodos(ListTodosRequest) returns (ListTodosResponse);
}

View File

@@ -1,32 +0,0 @@
package routers
import (
"git.kocoder.xyz/kocoded/vt/query"
"git.kocoder.xyz/kocoded/vt/utils"
"github.com/gofiber/fiber/v2"
)
type userRouter struct {
utils.Application
}
func RegisterUserRouter(group fiber.Router, appCtx utils.Application) {
router := &userRouter{Application: appCtx}
r := group.Use(utils.IsAuthenticated(appCtx))
r.Get("/current", router.getCurrentUserInfo)
}
func (r *userRouter) getCurrentUserInfo(c *fiber.Ctx) error {
u := query.User
session := c.Locals("USER_KEY").(*utils.Session)
currentUser, err := u.Where(u.ID.Eq(session.UserID)).First()
if err != nil {
r.Logger.Warn("Current mandant not found.", "error", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(currentUser)
}