diff --git a/src/app/eiffel/web.go b/src/app/eiffel/web.go index 4ef801e7e41cd7549bf189e7d009599609cd4b2c..b0778cefcb60e6d9ddf3cdb6125b705ca2a30aa7 100644 --- a/src/app/eiffel/web.go +++ b/src/app/eiffel/web.go @@ -455,7 +455,7 @@ func eiffelElicitationPage(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.H variantKey := web.URLParam(io.Request(), "variant") requirementID := io.Request().PostFormValue("requirementID") // POST-Wert abrufen editMode := io.Request().PostFormValue("edit") == "true" - + // Extract project ID from URL parameter or cookie projectID := io.Request().URL.Query().Get("project") if projectID == "" { @@ -464,7 +464,7 @@ func eiffelElicitationPage(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.H } } log.Printf("Extracted project ID from URL: %s", projectID) - + // Save project ID in cookie for subsequent requests if projectID != "" { cookie := &http.Cookie{ @@ -476,7 +476,7 @@ func eiffelElicitationPage(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.H } io.Response().Header().Add("Set-Cookie", cookie.String()) } - + // Prüfe, ob `templateID` leer ist, und rendere ein leeres Formular if templateID == "" { log.Println("templateID ist leer, rendere leeres Formular.") @@ -506,7 +506,7 @@ func eiffelElicitationPage(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.H formData.NeglectOptional = cfg.NeglectOptional formData.CopyAfterParse = CopyAfterParseSetting(io.Request(), sessionStore, true) - + // Set project ID from URL parameter if available if projectID != "" { formData.ProjectID = projectID @@ -645,7 +645,7 @@ func elicitationTemplate(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx, defaultF return web.NewController(appCtx, webCtx, func(io web.IO) error { templateID := web.URLParam(io.Request(), "templateID") variant := web.URLParam(io.Request(), "variant") - + // Extract project ID from URL parameter, form data, or session projectID := io.Request().URL.Query().Get("project") if projectID == "" { @@ -658,7 +658,7 @@ func elicitationTemplate(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx, defaultF } } log.Printf("Template selection: extracted project ID: %s", projectID) - + // Save project ID in cookie for subsequent requests if projectID != "" { cookie := &http.Cookie{ @@ -688,7 +688,7 @@ func elicitationTemplate(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx, defaultF formData.NeglectOptional = cfg.NeglectOptional formData.CopyAfterParse = CopyAfterParseSetting(io.Request(), sessionStore, true) - + // Set project ID from URL parameter if available if projectID != "" { formData.ProjectID = projectID @@ -714,13 +714,13 @@ func getProjectShortcut(ctx context.Context, projectID string) (string, error) { var project struct { Shortcut string `bson:"shortcut"` } - + err := projectCollection.FindOne(ctx, bson.M{"project_id": projectID}).Decode(&project) if err != nil { log.Printf("Error fetching project shortcut for project_id '%s': %v", projectID, err) return "", err } - + return project.Shortcut, nil } @@ -759,12 +759,12 @@ func saveToMongoDB(formData *TemplateFormData) error { if formData.ProjectID != "" { // UUID für diese Anforderung generieren document["uuid"] = uuid.New() - + // Shortcut basierend auf Projekt-Kürzel generieren if projectShortcut, err := getProjectShortcut(ctx, formData.ProjectID); err == nil && projectShortcut != "" { // Anzahl der existierenden Anforderungen für dieses Projekt abrufen count, _ := mongoCollection.CountDocuments(ctx, bson.M{"project_id": formData.ProjectID}) - + // Shortcut im Format: [PROJECT_SHORTCUT]-REQ-[NUMMER] (z.B. "HM-REQ-001") document["shortcut"] = fmt.Sprintf("%s-REQ-%03d", projectShortcut, count+1) } diff --git a/src/app/project/repository.go b/src/app/project/repository.go index 1750e39853be1f34bbeb4de93f4f4ca88f104a29..efa2098b3e48d8c140b2a59117f546f3f8790b25 100644 --- a/src/app/project/repository.go +++ b/src/app/project/repository.go @@ -50,9 +50,6 @@ type ProjectRepository interface { // Search finds projects matching a text query for a specific user Search(ctx context.Context, query string, userID uuid.UUID) ([]*Project, error) - - // CloneRequirement creates a copy of an existing requirement in a different project - CloneRequirement(ctx context.Context, sourceReqID primitive.ObjectID, targetProjectID string, newName string) error } // MongoProjectRepository implements ProjectRepository using MongoDB @@ -437,54 +434,3 @@ func (r *MongoProjectRepository) Search(ctx context.Context, query string, userI log.Printf("INFO: Search completed - found %d projects for query '%s'", len(projects), query) return projects, cursor.Err() } - -// CloneRequirement creates a copy of an existing requirement in a different project -func (r *MongoProjectRepository) CloneRequirement(ctx context.Context, sourceReqID primitive.ObjectID, targetProjectID string, newName string) error { - ctx, cancel := context.WithTimeout(ctx, r.timeout) - defer cancel() - - log.Printf("DEBUG: Cloning requirement %s to project %s with name '%s'", sourceReqID.Hex(), targetProjectID, newName) - - // Find the source requirement - var sourceReq bson.M - err := r.requirementCollection.FindOne(ctx, bson.M{"_id": sourceReqID}).Decode(&sourceReq) - if err != nil { - if err == mongo.ErrNoDocuments { - log.Printf("WARN: Source requirement %s not found", sourceReqID.Hex()) - return fmt.Errorf("source requirement not found") - } - log.Printf("ERROR: Failed to find source requirement %s: %v", sourceReqID.Hex(), err) - return fmt.Errorf("failed to find source requirement: %w", err) - } - - // Create cloned requirement with new ID and target project - clonedReq := bson.M{ - "_id": primitive.NewObjectID(), - "uuid": uuid.New(), - "project_id": targetProjectID, - "shortcut": newName, - "created_at": time.Now(), - "status": "Entwurf", - } - - // Copy relevant fields from source requirement - fieldsToClone := []string{"condition", "system", "requirement", "parsing_result"} - for _, field := range fieldsToClone { - if value, exists := sourceReq[field]; exists { - clonedReq[field] = value - } - } - - // Generate requirement_id based on shortcut - clonedReq["requirement_id"] = newName - - // Insert the cloned requirement - _, err = r.requirementCollection.InsertOne(ctx, clonedReq) - if err != nil { - log.Printf("ERROR: Failed to insert cloned requirement: %v", err) - return fmt.Errorf("failed to clone requirement: %w", err) - } - - log.Printf("INFO: Requirement cloned successfully to project %s", targetProjectID) - return nil -} diff --git a/src/app/project/service.go b/src/app/project/service.go index c414b925166c2c22b89e9de64d7505a03f511885..dfbdbd7c5562d769175f432a0570d3c6f595db68 100644 --- a/src/app/project/service.go +++ b/src/app/project/service.go @@ -194,27 +194,3 @@ func (s *ProjectService) SearchProjects(ctx context.Context, query string, userI log.Printf("INFO: Search completed - found %d projects for query '%s'", len(projects), query) return projects, nil } - -// CloneRequirement handles the cloning of a requirement to a different project -func (s *ProjectService) CloneRequirement(ctx context.Context, sourceReqID primitive.ObjectID, targetProjectID string, newName string) error { - log.Printf("INFO: Cloning requirement %s to project %s with name '%s'", sourceReqID.Hex(), targetProjectID, newName) - - // Validate that the target project exists - targetProject, err := s.repo.FindByProjectID(ctx, targetProjectID) - if err != nil { - log.Printf("ERROR: Target project '%s' not found: %v", targetProjectID, err) - return fmt.Errorf("target project not found") - } - - log.Printf("DEBUG: Target project '%s' found, proceeding with clone", targetProject.Name) - - // Delegate to repository for the actual cloning operation - err = s.repo.CloneRequirement(ctx, sourceReqID, targetProjectID, newName) - if err != nil { - log.Printf("ERROR: Failed to clone requirement %s: %v", sourceReqID.Hex(), err) - return err - } - - log.Printf("INFO: Requirement cloned successfully to project '%s'", targetProject.Name) - return nil -} diff --git a/src/app/project/web/web.go b/src/app/project/web/web.go index c3b46f930fe5d24cdeb51c4ffceaeb36239eaf7d..5ee83471b654611cc87f9b9a2a1216bd3d7d7444 100644 --- a/src/app/project/web/web.go +++ b/src/app/project/web/web.go @@ -36,15 +36,6 @@ type ProjectDetailData struct { RequirementStatuses []string // Available status options for requirements } -// RequirementCloneFormData is passed to the requirement clone modal -type RequirementCloneFormData struct { - Name string `hvalidate:"required"` - ProjectID string - Requirement *project.RequirementSummary - Projects []*project.Project - Cloned bool -} - // RegisterController sets up all HTTP routes and handlers for project management // This function is called during application startup to configure the web routing func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx, mongoManager *project.MongoManager) { @@ -96,12 +87,6 @@ func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx, mongoManager *proj // POST /project/search - Handle project search functionality router.Post("/project/search", projectSearchController(appCtx, webCtx, projectService).ServeHTTP) - // GET /requirement/{id}/clone/modal - Show requirement clone modal - router.Get("/requirement/{id}/clone/modal", requirementCloneModalController(appCtx, webCtx, projectService).ServeHTTP) - - // POST /requirement/{id}/clone - Process requirement cloning - router.Post("/requirement/{id}/clone", requirementCloneController(appCtx, webCtx, projectService).ServeHTTP) - log.Println("INFO: Project routes registered successfully") } @@ -546,129 +531,3 @@ func renderProjectEditForm(io web.IO, toUpdate *project.ProjectToUpdate, success return err } - -// requirementCloneModalController displays the modal for cloning a requirement -func requirementCloneModalController(appCtx *hctx.AppCtx, webCtx *web.Ctx, service *project.ProjectService) http.Handler { - return web.NewController(appCtx, webCtx, func(io web.IO) error { - ctx := io.Context() - usr := user.MustCtxUser(ctx) - - // Parse requirement ID from URL - reqID, err := parseObjectID(io.Request(), "id") - if err != nil { - return io.InlineError(web.ErrInternal, err) - } - - // Find the requirement by searching through all projects - var targetReq *project.RequirementSummary - projects, err := service.GetProjectsByUser(ctx, usr.ID) - if err != nil { - return io.InlineError(web.ErrInternal, err) - } - - for _, proj := range projects { - projWithReqs, err := service.GetProjectWithRequirements(ctx, proj.ID) - if err != nil { - continue - } - for _, req := range projWithReqs.Requirements { - if req.ID == reqID { - targetReq = req - break - } - } - if targetReq != nil { - break - } - } - - if targetReq == nil { - return io.InlineError(web.ErrInternal, errors.New("requirement not found")) - } - - return io.Render(web.NewFormData(RequirementCloneFormData{ - Requirement: targetReq, - Projects: projects, - }, nil), "requirement.clone.modal", "project/_modal-clone-requirement.go.html") - }) -} - -// requirementCloneController processes the requirement cloning form submission -func requirementCloneController(appCtx *hctx.AppCtx, webCtx *web.Ctx, service *project.ProjectService) http.Handler { - return web.NewController(appCtx, webCtx, func(io web.IO) error { - ctx := io.Context() - usr := user.MustCtxUser(ctx) - - log.Printf("INFO: Starting requirement clone process for user %s", usr.ID) - - // Parse requirement ID from URL - reqID, err := parseObjectID(io.Request(), "id") - if err != nil { - log.Printf("ERROR: Failed to parse requirement ID: %v", err) - return io.InlineError(web.ErrInternal, err) - } - - log.Printf("INFO: Cloning requirement with ID %s", reqID.Hex()) - - // Get projects for form data - projects, err := service.GetProjectsByUser(ctx, usr.ID) - if err != nil { - return io.InlineError(web.ErrInternal, err) - } - - // Find the source requirement - var sourceReq *project.RequirementSummary - for _, proj := range projects { - projWithReqs, err := service.GetProjectWithRequirements(ctx, proj.ID) - if err != nil { - continue - } - for _, req := range projWithReqs.Requirements { - if req.ID == reqID { - sourceReq = req - break - } - } - if sourceReq != nil { - break - } - } - - if sourceReq == nil { - log.Printf("ERROR: Source requirement %s not found", reqID.Hex()) - return io.InlineError(web.ErrInternal, errors.New("source requirement not found")) - } - - log.Printf("INFO: Found source requirement %s", sourceReq.RequirementID) - - formData := &RequirementCloneFormData{Requirement: sourceReq, Projects: projects} - err, validationErrs := web.ReadForm(io.Request(), formData, appCtx.Validator) - if err != nil { - log.Printf("ERROR: Failed to read form: %v", err) - return io.InlineError(web.ErrInternal, err) - } - - log.Printf("INFO: Form data - ProjectID: %s, Name: %s", formData.ProjectID, formData.Name) - - if validationErrs != nil { - log.Printf("WARN: Validation errors: %v", validationErrs) - return io.Render(web.NewFormData(formData, nil, validationErrs...), "requirement.clone.modal", "project/_modal-clone-requirement.go.html") - } - - // Clone the requirement - log.Printf("INFO: Calling service.CloneRequirement with ProjectID: %s, Name: %s", formData.ProjectID, formData.Name) - err = service.CloneRequirement(ctx, reqID, formData.ProjectID, formData.Name) - if err != nil { - log.Printf("ERROR: Clone failed: %v", err) - return io.InlineError(web.ErrInternal, err) - } - - log.Printf("INFO: Requirement cloned successfully") - - // Return a simple success response for API calls - io.Response().Header().Set("Content-Type", "text/plain") - io.Response().WriteHeader(http.StatusOK) - _, err = io.Response().Write([]byte("Anforderung erfolgreich geklont")) - return err - }) -} diff --git a/templates/project/_requirements-list.go.html b/templates/project/_requirements-list.go.html index d485ca1c73f82ff51a115d03f3b9990414dbeb20..c4c6b1ea6483326877acb5ee85c18f656bffd0e7 100644 --- a/templates/project/_requirements-list.go.html +++ b/templates/project/_requirements-list.go.html @@ -78,9 +78,7 @@ <td> <div class="btn-group btn-group-sm"> <!-- Clone requirement button --> - <button onclick="cloneRequirement('{{ .ID.Hex }}', '{{ .RequirementID }}')" - class="btn btn-outline-secondary" - title="Klonen"> + <button class="btn btn-outline-secondary" title="Klonen"> 📋 </button> <!-- Delete requirement button --> @@ -96,55 +94,4 @@ </div> {{ end }} </div> - -<script> -function cloneRequirement(reqId, reqName) { - // Get list of available projects from the current page context - const projects = [ - {{ range $.Data.Project.Project.CreatedBy }} - // This won't work, we need to get projects differently - {{ end }} - ]; - - // Simple prompt-based solution for now - const targetProjectId = prompt(`Anforderung "${reqName}" klonen.\n\nGeben Sie die Projekt-ID des Zielprojekts ein:`); - if (!targetProjectId) return; - - const newName = prompt(`Neuer Name für die geklonte Anforderung:`, `${reqName}-kopie`); - if (!newName) return; - - // Debug: Log the request details - console.log('Making clone request:', { - url: `/requirement/${reqId}/clone`, - targetProjectId: targetProjectId, - newName: newName - }); - - // Make the clone request - fetch(`/requirement/${reqId}/clone`, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `ProjectID=${encodeURIComponent(targetProjectId)}&Name=${encodeURIComponent(newName)}` - }) - .then(response => { - console.log('Response status:', response.status); - console.log('Response headers:', response.headers); - return response.text().then(text => { - console.log('Response body:', text); - if (response.ok) { - alert(`Anforderung erfolgreich nach Projekt "${targetProjectId}" geklont!`); - location.reload(); // Refresh to show changes - } else { - alert(`Fehler beim Klonen der Anforderung: ${response.status} - ${text}`); - } - }); - }) - .catch(error => { - console.error('Error:', error); - alert('Fehler beim Klonen der Anforderung: ' + error.message); - }); -} -</script> {{ end }} \ No newline at end of file