diff --git a/Internal/adapters/repository/dynamicDBRepo.go b/Internal/adapters/repository/dynamicDBRepo.go new file mode 100644 index 0000000..36f0081 --- /dev/null +++ b/Internal/adapters/repository/dynamicDBRepo.go @@ -0,0 +1,45 @@ +package repository + +import ( + "github.com/Arjuna-Ragil/Localbase/Internal/core/domain" + "gorm.io/gorm" +) + +type DynamicDBRepo struct { + DB *DBContainer +} + +func NewDynamicDB(db *DBContainer) *DynamicDBRepo { + return &DynamicDBRepo{DB: db} +} + +func (dr *DynamicDBRepo) RunDynamicQuery(tx *gorm.DB, query string) error { + if err := tx.Exec(query).Error; err != nil { + return err + } + return nil +} + +// Dynamic database note + +func (dr *DynamicDBRepo) CreateDynTable(tx *gorm.DB, dynTable *domain.DynTableDef) (*domain.DynTableDef, error) { + if err := tx.Create(&dynTable).Error; err != nil { + return nil, err + } + return dynTable, nil +} + +func (dr *DynamicDBRepo) CreateDynCol(tx *gorm.DB, dynCol *domain.DynColDef) error { + if err := tx.Create(&dynCol).Error; err != nil { + return err + } + return nil +} + +func (dr *DynamicDBRepo) GetTables(projectID uint) ([]domain.DynTableDef, error) { + var tables []domain.DynTableDef + if err := dr.DB.Gorm.Preload("Columns").Where("project_id = ?", projectID).Find(&tables).Error; err != nil { + return nil, err + } + return tables, nil +} diff --git a/Internal/adapters/repository/postgres.go b/Internal/adapters/repository/postgres.go index e1974f5..3ba08a8 100644 --- a/Internal/adapters/repository/postgres.go +++ b/Internal/adapters/repository/postgres.go @@ -41,6 +41,10 @@ func (db *DBContainer) Migrate() error { err := db.Gorm.AutoMigrate( &domain.User{}, &domain.Invitation{}, + &domain.Project{}, + &domain.ProjectUser{}, + &domain.DynTableDef{}, + &domain.DynColDef{}, ) if err != nil { log.Fatalf("Failed to migrate users: %v", err) diff --git a/Internal/adapters/repository/projectRepo.go b/Internal/adapters/repository/projectRepo.go new file mode 100644 index 0000000..15d8a51 --- /dev/null +++ b/Internal/adapters/repository/projectRepo.go @@ -0,0 +1,58 @@ +package repository + +import ( + "github.com/Arjuna-Ragil/Localbase/Internal/core/domain" +) + +type ProjectRepo struct { + DB *DBContainer +} + +func NewProjectRepo(db *DBContainer) *ProjectRepo { + return &ProjectRepo{DB: db} +} + +func (pr *ProjectRepo) CreateProject(project *domain.Project) (*domain.Project, error) { + if err := pr.DB.Gorm.Create(project).Error; err != nil { + return nil, err + } + return project, nil +} + +func (pr *ProjectRepo) FetchProjects(projects []domain.Project) ([]domain.Project, error) { + if err := pr.DB.Gorm.Find(&projects).Error; err != nil { + return nil, err + } + return projects, nil +} + +func (pr *ProjectRepo) ProjectById(id string) (*domain.Project, error) { + var project domain.Project + if err := pr.DB.Gorm.Preload("ProjectUsers.User").First(&project, id).Error; err != nil { + return nil, err + } + return &project, nil +} + +func (pr *ProjectRepo) InviteProject(projectuser *domain.ProjectUser) (*domain.ProjectUser, error) { + if err := pr.DB.Gorm.Create(projectuser).Error; err != nil { + return nil, err + } + return projectuser, nil +} + +func (pr *ProjectRepo) ProjectUserCheck(userID uint) (*domain.ProjectUser, error) { + var projectuser domain.ProjectUser + if err := pr.DB.Gorm.First(&projectuser, userID).Error; err != nil { + return nil, err + } + return &projectuser, nil +} + +func (pr *ProjectRepo) RemoveProjectUser(id uint) error { + var projectuser domain.ProjectUser + if err := pr.DB.Gorm.Delete(&projectuser, id).Error; err != nil { + return err + } + return nil +} diff --git a/Internal/adapters/repository/userRepo.go b/Internal/adapters/repository/userRepo.go index e965fbd..0015945 100644 --- a/Internal/adapters/repository/userRepo.go +++ b/Internal/adapters/repository/userRepo.go @@ -17,12 +17,21 @@ func NewUserRepository(db *DBContainer) *UserRepository { // Query func (ur *UserRepository) save(user *domain.User) error { + return ur.db.Gorm.Create(user).Error } func (ur *UserRepository) FindById(id uint) (*domain.User, error) { var user domain.User - err := ur.db.Gorm.First(&user, id).Error + if err := ur.db.Gorm.Preload("Projects.Project").First(&user, id).Error; err != nil { + return nil, err + } + return &user, nil +} + +func (ur *UserRepository) FindByEmail(email string) (*domain.User, error) { + var user domain.User + err := ur.db.Gorm.Where("email = ?", email).First(&user).Error if err != nil { return nil, err } diff --git a/Internal/api/Routes/routev1.go b/Internal/api/Routes/routev1.go index 2a50cb8..d1e070f 100644 --- a/Internal/api/Routes/routev1.go +++ b/Internal/api/Routes/routev1.go @@ -9,11 +9,13 @@ import ( ) type Deps struct { - User *handlers.UserHandler - Auth *handlers.AuthHandler - System *handlers.SystemHandler - UserRepo *repository.UserRepository - Config *config.Config + User *handlers.UserHandler + Auth *handlers.AuthHandler + System *handlers.SystemHandler + Project *handlers.ProjectHandler + DynamicDB *handlers.DynamicDBHandler + UserRepo *repository.UserRepository + Config *config.Config } func SetupRouterV1(r *gin.Engine, deps Deps) { @@ -45,6 +47,21 @@ func SetupRouterV1(r *gin.Engine, deps Deps) { admin.POST("/invite", deps.Auth.CreateInviteHandler) admin.GET("/alluser", deps.User.AllUserHandler) admin.PUT("/updaterole", deps.User.UpdateRoleHandler) + admin.POST("/createproject", deps.Project.CreateProjectHandler) + } + project := protected.Group("/project") + { + project.GET("/projects", deps.Project.GetAllProjectHandler) + project.GET("/:projectid", deps.Project.GetProjectHandler) + project.GET("/:projectid/tables", deps.DynamicDB.GetDynTablesHandler) + + manage := project.Group("/manage") + { + manage.POST("/invite/:projectid", deps.Project.InviteProjectHandler) + manage.DELETE("/remove/:projuserid", deps.Project.RemoveProjectUserHandler) + manage.POST("/create/table/:projectid", deps.DynamicDB.CreateDynamicDBHandler) + manage.POST("/create/col/:projectid/:tableid", deps.DynamicDB.AddDynamicColHandler) + } } } } diff --git a/Internal/api/handlers/dynamicDBHandler.go b/Internal/api/handlers/dynamicDBHandler.go new file mode 100644 index 0000000..493999a --- /dev/null +++ b/Internal/api/handlers/dynamicDBHandler.go @@ -0,0 +1,119 @@ +package handlers + +import ( + "strconv" + + "github.com/Arjuna-Ragil/Localbase/Internal/core/services" + "github.com/gin-gonic/gin" +) + +type DynamicDBHandler struct { + Serv *services.DynamicDBService +} + +func NewDynamicDBHandler(serv *services.DynamicDBService) *DynamicDBHandler { + return &DynamicDBHandler{Serv: serv} +} + +func (dh *DynamicDBHandler) CreateDynamicDBHandler(c *gin.Context) { + projectIDStr := c.Param("projectid") + projectID, err := strconv.Atoi(projectIDStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Project ID not valid", + "error": err.Error(), + }) + return + } + var input services.DDLTableReq + if err = c.ShouldBindJSON(&input); err != nil { + c.JSON(400, gin.H{ + "message": "Invalid json body", + "error": err.Error(), + }) + return + } + DynTable, err := dh.Serv.CreateDynamicTable(uint(projectID), input) + if nil != err { + c.JSON(500, gin.H{ + "message": "Failed to create dynamic table", + "error": err.Error(), + }) + return + } + if err = dh.Serv.CreateDynamicCols(DynTable.ID, uint(projectID), input); nil != err { + c.JSON(500, gin.H{ + "message": "Failed to create dynamic cols", + "error": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "Successfully created dynamic table", + "data": nil, + }) +} + +func (dh *DynamicDBHandler) AddDynamicColHandler(c *gin.Context) { + projectIDStr := c.Param("projectid") + projectID, err := strconv.Atoi(projectIDStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Project ID not valid", + "error": err.Error(), + }) + return + } + tableIDStr := c.Param("tableid") + tableID, err := strconv.Atoi(tableIDStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Table ID not valid", + "error": err.Error(), + }) + return + } + var input services.DDLTableReq + if err = c.ShouldBindJSON(&input); err != nil { + c.JSON(400, gin.H{ + "message": "Invalid json body", + "error": err.Error(), + }) + return + } + if err = dh.Serv.CreateDynamicCols(uint(tableID), uint(projectID), input); nil != err { + c.JSON(500, gin.H{ + "message": "Failed to create dynamic cols", + "error": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "Successfully added dynamic col", + "data": nil, + }) +} + +func (dh *DynamicDBHandler) GetDynTablesHandler(c *gin.Context) { + projectIDStr := c.Param("projectid") + projectID, err := strconv.Atoi(projectIDStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Project ID not valid", + "error": err.Error(), + }) + return + } + tables, err := dh.Serv.GetDynamicTables(uint(projectID)) + if err != nil { + c.JSON(500, gin.H{ + "message": "Failed to get dynamic table", + "error": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "Successfully get dynamic table", + "data": tables, + }) +} diff --git a/Internal/api/handlers/projectHandler.go b/Internal/api/handlers/projectHandler.go new file mode 100644 index 0000000..e302d32 --- /dev/null +++ b/Internal/api/handlers/projectHandler.go @@ -0,0 +1,140 @@ +package handlers + +import ( + "strconv" + + "github.com/Arjuna-Ragil/Localbase/Internal/core/services" + "github.com/gin-gonic/gin" +) + +type ProjectHandler struct { + ProjectService *services.ProjectService +} + +func NewProjectHandler(projectService *services.ProjectService) *ProjectHandler { + return &ProjectHandler{ProjectService: projectService} +} + +func (ph *ProjectHandler) CreateProjectHandler(c *gin.Context) { + var input services.CreateInput + userRole, _ := c.Get("userRole") + if userRole != "admin" { + c.JSON(401, gin.H{ + "message": "You are not authorized to perform this action", + "data": nil, + }) + return + } + err := c.ShouldBindJSON(&input) + if err != nil { + c.JSON(400, gin.H{ + "message": "Invalid input", + "data": err.Error(), + }) + return + } + + project, err := ph.ProjectService.CreateProject(&input) + if err != nil { + c.JSON(500, gin.H{ + "message": "failed to create project", + "data": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "successfully created project", + "data": project, + }) +} + +func (ph *ProjectHandler) GetAllProjectHandler(c *gin.Context) { + userRole := c.GetString("userRole") + projects, err := ph.ProjectService.GetProjects(userRole) + if err != nil { + c.JSON(500, gin.H{ + "message": "failed to get projects", + "data": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "got all projects", + "data": projects, + }) +} + +func (ph *ProjectHandler) GetProjectHandler(c *gin.Context) { + projectId := c.Param("projectid") + project, err := ph.ProjectService.GetProjectById(projectId) + if err != nil { + c.JSON(500, gin.H{ + "message": "failed to get project", + "data": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "success in fetching project", + "data": project, + }) +} + +func (ph *ProjectHandler) InviteProjectHandler(c *gin.Context) { + projectIDStr := c.Param("projectid") + projectID, err := strconv.Atoi(projectIDStr) + if err != nil { + c.JSON(400, gin.H{ + "message": "Invalid Project ID", + "error": err.Error(), + }) + return + } + userRole := c.GetString("userRole") + userID := c.GetUint("userId") + var input services.InviteProjectInput + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(400, gin.H{ + "message": "Invalid input", + "data": err.Error(), + }) + return + } + invite, err := ph.ProjectService.InviteProject(&input, userRole, userID, uint(projectID)) + if err != nil { + c.JSON(500, gin.H{ + "message": "failed to invite project", + "data": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "successfully invited project", + "data": invite, + }) +} + +func (ph *ProjectHandler) RemoveProjectUserHandler(c *gin.Context) { + projectUserSTR := c.Param("projuserid") + projectUserID, err := strconv.Atoi(projectUserSTR) + if err != nil { + c.JSON(400, gin.H{ + "message": "Invalid Project ID", + "error": err.Error(), + }) + return + } + userRole := c.GetString("userRole") + userID := c.GetUint("userId") + if err := ph.ProjectService.DeleteProjectUser(uint(projectUserID), userRole, userID); err != nil { + c.JSON(500, gin.H{ + "message": "failed to remove project user", + "data": err.Error(), + }) + return + } + c.JSON(200, gin.H{ + "message": "successfully removed project user", + "data": nil, + }) +} diff --git a/Internal/api/handlers/userHandler.go b/Internal/api/handlers/userHandler.go index 26fb067..679ebf8 100644 --- a/Internal/api/handlers/userHandler.go +++ b/Internal/api/handlers/userHandler.go @@ -60,14 +60,14 @@ func (uh *UserHandler) AllUserHandler(c *gin.Context) { func (uh *UserHandler) UpdateRoleHandler(c *gin.Context) { var input services.RoleInput - //userRole, _ := c.Get("userRole") - //if userRole != "admin" { - // c.JSON(400, gin.H{ - // "message": "Not allowed to update role", - // "data": false, - // }) - // return - //} + userRole, _ := c.Get("userRole") + if userRole != "admin" { + c.JSON(400, gin.H{ + "message": "Not allowed to update role", + "data": false, + }) + return + } if err := c.ShouldBindJSON(&input); err != nil { c.JSON(400, gin.H{ "message": "input not valid", diff --git a/Internal/api/middleware/auth.go b/Internal/api/middleware/auth.go index d9a6038..e77d2e4 100644 --- a/Internal/api/middleware/auth.go +++ b/Internal/api/middleware/auth.go @@ -14,7 +14,8 @@ import ( func AuthMiddleware(userRepo *repository.UserRepository, cfg *config.Config) gin.HandlerFunc { return func(c *gin.Context) { if cfg.AuthMode == "false" { - c.Set("userID", 1) + c.Set("userID", uint(1)) + c.Set("userRole", "admin") c.Next() return } @@ -26,6 +27,7 @@ func AuthMiddleware(userRepo *repository.UserRepository, cfg *config.Config) gin "message": "Authorization header not found", "data": err.Error(), }) + c.Abort() return } parts := strings.Split(authHeader, " ") @@ -69,7 +71,7 @@ func AuthMiddleware(userRepo *repository.UserRepository, cfg *config.Config) gin return } - c.Set("userID", user.Id) + c.Set("userID", user.ID) c.Set("userRole", user.Role) c.Next() diff --git a/Internal/core/domain/projectdb.go b/Internal/core/domain/projectdb.go new file mode 100644 index 0000000..081e42d --- /dev/null +++ b/Internal/core/domain/projectdb.go @@ -0,0 +1,37 @@ +package domain + +import "time" + +type Project struct { + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + Name string `gorm:"size:255;not null" json:"name"` + Desc string `gorm:"size:255;" json:"desc"` + AdminID uint `json:"admin_id"` + ProjectUsers []ProjectUser `json:"project_users,omitempty"` + CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` +} + +type ProjectUser struct { + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + ProjectID uint `json:"project_id"` + UserID uint `json:"user_id"` + Project *Project `json:"project,omitempty"` + User *User `json:"user,omitempty"` + Role string `gorm:"size:255;not null" json:"role"` + CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` +} + +type DynTableDef struct { + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + ProjectID uint `gorm:"not null" json:"project_id"` + Name string `gorm:"size:255;not null" json:"name"` + Alias string `gorm:"size:255;not null" json:"alias"` + Columns []DynColDef `gorm:"foreignKey:DynTableDefID" json:"columns"` +} + +type DynColDef struct { + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + DynTableDefID uint `gorm:"not null" json:"dyn_table_def_id"` + Name string `gorm:"size:255;not null" json:"name"` + DataType string `gorm:"size:255;not null" json:"data_type"` +} diff --git a/Internal/core/domain/userdb.go b/Internal/core/domain/userdb.go index 110598d..e117373 100644 --- a/Internal/core/domain/userdb.go +++ b/Internal/core/domain/userdb.go @@ -8,15 +8,18 @@ import ( ) type User struct { - Id uint `gorm:"primary_key;AUTO_INCREMENT" json:"id"` - Email string `gorm:"size:255;unique;not null" json:"email"` - Password string `json:"-"` - Username string `gorm:"size:255;unique;not null" json:"username"` - Role string `gorm:"size:255;not null" json:"role"` - CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` - UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + Email string `gorm:"size:255;unique;not null" json:"email"` + Password string `json:"-"` + Username string `gorm:"size:255;unique;not null" json:"username"` + Role string `gorm:"size:255;not null" json:"role"` + Projects []ProjectUser `json:"projects"` + CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` + UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` } +//last chnages savepoint + func (u *User) BeforeCreate(tx *gorm.DB) (err error) { if len(u.Password) < 50 { hashed, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) diff --git a/Internal/core/services/authService.go b/Internal/core/services/authService.go index 8c7145f..17506d2 100644 --- a/Internal/core/services/authService.go +++ b/Internal/core/services/authService.go @@ -95,7 +95,7 @@ func (as *AuthService) LoginService(input *LoginInput) (string, error) { return "", err } token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "sub": user.Id, + "sub": user.ID, "exp": time.Now().Add(time.Hour * 48).Unix(), }) tokenString, err := token.SignedString([]byte(cfg.SecretKey)) diff --git a/Internal/core/services/dynamicDBService.go b/Internal/core/services/dynamicDBService.go new file mode 100644 index 0000000..f0f8166 --- /dev/null +++ b/Internal/core/services/dynamicDBService.go @@ -0,0 +1,128 @@ +package services + +import ( + "fmt" + "regexp" + "strings" + + "github.com/Arjuna-Ragil/Localbase/Internal/adapters/repository" + "github.com/Arjuna-Ragil/Localbase/Internal/core/domain" +) + +type DynamicDBService struct { + Repo *repository.DynamicDBRepo + DB *repository.DBContainer +} + +func NewDynamicDBService(repo *repository.DynamicDBRepo, db *repository.DBContainer) *DynamicDBService { + return &DynamicDBService{Repo: repo, DB: db} +} + +type ColumnReq struct { + Name string `json:"name"` + Type string `json:"type"` +} + +type DDLTableReq struct { + TableName string `json:"table_name"` + Columns []ColumnReq `json:"columns"` +} + +func sanitizeInput(input string) string { + result := strings.ToLower(input) + result = strings.ReplaceAll(result, " ", "_") + reg := regexp.MustCompile("[^a-z0-9_]+") + result = reg.ReplaceAllString(result, "") + return result +} + +func (ds *DynamicDBService) CreateDynamicTable(projectID uint, req DDLTableReq) (*domain.DynTableDef, error) { + tx := ds.DB.Gorm.Begin() + + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + safeTableName := fmt.Sprintf("proj_%d_%s", projectID, sanitizeInput(req.TableName)) + + tableDef := domain.DynTableDef{ + ProjectID: projectID, + Name: safeTableName, + Alias: req.TableName, + } + + DynTable, err := ds.Repo.CreateDynTable(tx, &tableDef) + if err != nil { + tx.Rollback() + return nil, err + } + + var queryBuilder strings.Builder + queryBuilder.WriteString(fmt.Sprintf("CREATE TABLE %s (", safeTableName)) + queryBuilder.WriteString("id SERIAL PRIMARY KEY, ") + queryBuilder.WriteString("created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ") + + query := queryBuilder.String() + query = strings.TrimSuffix(query, ", ") + query += ");" + + fmt.Println(query) + if err = ds.Repo.RunDynamicQuery(tx, query); err != nil { + tx.Rollback() + return nil, err + } + return DynTable, tx.Commit().Error +} + +func (ds *DynamicDBService) CreateDynamicCols(tableID uint, projectID uint, req DDLTableReq) error { + tx := ds.DB.Gorm.Begin() + + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + safeTableName := fmt.Sprintf("proj_%d_%s", projectID, sanitizeInput(req.TableName)) + + if len(req.Columns) == 0 { + return fmt.Errorf("no columns specified") + } + var queryBuilder strings.Builder + queryBuilder.WriteString(fmt.Sprintf("ALTER TABLE %s ", safeTableName)) + + for i, col := range req.Columns { + safeColName := sanitizeInput(col.Name) + coldef := domain.DynColDef{ + DynTableDefID: tableID, + Name: col.Name, + DataType: col.Type, + } + if err := ds.Repo.CreateDynCol(tx, &coldef); err != nil { + tx.Rollback() + return err + } + + queryBuilder.WriteString(fmt.Sprintf("ADD COLUMN %s %s", safeColName, col.Type)) + if i != len(req.Columns)-1 { + queryBuilder.WriteString(", ") + } + } + queryBuilder.WriteString(";") + query := queryBuilder.String() + + if err := ds.Repo.RunDynamicQuery(tx, query); err != nil { + return err + } + return tx.Commit().Error +} + +func (ds *DynamicDBService) GetDynamicTables(projectID uint) ([]domain.DynTableDef, error) { + tables, err := ds.Repo.GetTables(projectID) + if err != nil { + return nil, err + } + return tables, nil +} diff --git a/Internal/core/services/projectService.go b/Internal/core/services/projectService.go new file mode 100644 index 0000000..aec2a14 --- /dev/null +++ b/Internal/core/services/projectService.go @@ -0,0 +1,103 @@ +package services + +import ( + "errors" + + "github.com/Arjuna-Ragil/Localbase/Internal/adapters/repository" + "github.com/Arjuna-Ragil/Localbase/Internal/core/domain" +) + +type ProjectService struct { + ProjectRepo *repository.ProjectRepo + UserRepo *repository.UserRepository +} + +func NewProjectService(project *repository.ProjectRepo, user *repository.UserRepository) *ProjectService { + return &ProjectService{ProjectRepo: project, UserRepo: user} +} + +type CreateInput struct { + Name string `json:"name"` + Desc string `json:"desc"` + AdminID uint `json:"admin_id"` +} + +type InviteProjectInput struct { + Email string `json:"email"` + Role string `json:"role"` +} + +func (ps *ProjectService) CreateProject(input *CreateInput) (*domain.Project, error) { + projectInfo := domain.Project{ + Name: input.Name, + Desc: input.Desc, + AdminID: input.AdminID, + } + project, err := ps.ProjectRepo.CreateProject(&projectInfo) + if err != nil { + return nil, err + } + return project, nil +} + +func (ps *ProjectService) GetProjects(Role string) ([]domain.Project, error) { + if Role != "admin" { + return nil, errors.New("permission denied") + } + var projects []domain.Project + project, err := ps.ProjectRepo.FetchProjects(projects) + if err != nil { + return nil, err + } + return project, nil +} + +func (ps *ProjectService) GetProjectById(id string) (*domain.Project, error) { + project, err := ps.ProjectRepo.ProjectById(id) + if err != nil { + return nil, err + } + return project, nil +} + +func (ps *ProjectService) InviteProject(input *InviteProjectInput, Role string, userID uint, projectID uint) (*domain.ProjectUser, error) { + if Role != "admin" { + prorole, err := ps.ProjectRepo.ProjectUserCheck(userID) + if err != nil { + return nil, errors.New("user is not part of project") + } + if prorole.Role != "lead" { + return nil, errors.New("permission denied") + } + } + user, err := ps.UserRepo.FindByEmail(input.Email) + if err != nil { + return nil, err + } + var inviteInfo = domain.ProjectUser{ + ProjectID: projectID, + UserID: user.ID, + Role: input.Role, + } + project, err := ps.ProjectRepo.InviteProject(&inviteInfo) + if err != nil { + return nil, err + } + return project, nil +} + +func (ps *ProjectService) DeleteProjectUser(id uint, Role string, userID uint) error { + if Role != "admin" { + prorole, err := ps.ProjectRepo.ProjectUserCheck(userID) + if err != nil { + return errors.New("user is not part of project") + } + if prorole.Role != "lead" { + return errors.New("permission denied") + } + } + if err := ps.ProjectRepo.RemoveProjectUser(id); err != nil { + return err + } + return nil +} diff --git a/Lb-web/src/components/ui/textarea.tsx b/Lb-web/src/components/ui/textarea.tsx new file mode 100644 index 0000000..87c2072 --- /dev/null +++ b/Lb-web/src/components/ui/textarea.tsx @@ -0,0 +1,24 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface TextareaProps + extends React.TextareaHTMLAttributes { } + +const Textarea = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +