getStatistics handler, db-interface and ts API
This commit is contained in:
		
							parent
							
								
									2d2b63938c
								
							
						
					
					
						commit
						fe9d5f74bb
					
				
					 8 changed files with 188 additions and 1 deletions
				
			
		| 
						 | 
					@ -2,11 +2,13 @@ package database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"embed"
 | 
						"embed"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"ttime/internal/types"
 | 
						"ttime/internal/types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gofiber/fiber/v2/log"
 | 
				
			||||||
	"github.com/jmoiron/sqlx"
 | 
						"github.com/jmoiron/sqlx"
 | 
				
			||||||
	_ "modernc.org/sqlite"
 | 
						_ "modernc.org/sqlite"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -41,6 +43,7 @@ type Database interface {
 | 
				
			||||||
	SignWeeklyReport(reportId int, projectManagerId int) error
 | 
						SignWeeklyReport(reportId int, projectManagerId int) error
 | 
				
			||||||
	IsSiteAdmin(username string) (bool, error)
 | 
						IsSiteAdmin(username string) (bool, error)
 | 
				
			||||||
	IsProjectManager(username string, projectname string) (bool, error)
 | 
						IsProjectManager(username string, projectname string) (bool, error)
 | 
				
			||||||
 | 
						ReportStatistics(username string, projectName string) (*types.Statistics, error)
 | 
				
			||||||
	GetProjectTimes(projectName string) (map[string]int, error)
 | 
						GetProjectTimes(projectName string) (map[string]int, error)
 | 
				
			||||||
	UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
 | 
						UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
 | 
				
			||||||
	RemoveProject(projectname string) error
 | 
						RemoveProject(projectname string) error
 | 
				
			||||||
| 
						 | 
					@ -94,6 +97,17 @@ const removeUserFromProjectQuery = `DELETE FROM user_roles
 | 
				
			||||||
									WHERE user_id = (SELECT id FROM users WHERE username = ?) 
 | 
														WHERE user_id = (SELECT id FROM users WHERE username = ?) 
 | 
				
			||||||
									AND project_id = (SELECT id FROM projects WHERE name = ?)`
 | 
														AND project_id = (SELECT id FROM projects WHERE name = ?)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reportStatistics = `SELECT SUM(development_time) AS total_development_time,
 | 
				
			||||||
 | 
												SUM(meeting_time) AS total_meeting_time,
 | 
				
			||||||
 | 
												SUM(admin_time) AS total_admin_time,
 | 
				
			||||||
 | 
												SUM(own_work_time) AS total_own_work_time,
 | 
				
			||||||
 | 
												SUM(study_time) AS total_study_time,
 | 
				
			||||||
 | 
												SUM(testing_time) AS total_testing_time
 | 
				
			||||||
 | 
												FROM weekly_reports
 | 
				
			||||||
 | 
												WHERE user_id = (SELECT id FROM users WHERE username = ?)
 | 
				
			||||||
 | 
												AND project_id = (SELECT id FROM projects WHERE name = ?)
 | 
				
			||||||
 | 
												GROUP BY user_id, project_id`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DbConnect connects to the database
 | 
					// DbConnect connects to the database
 | 
				
			||||||
func DbConnect(dbpath string) Database {
 | 
					func DbConnect(dbpath string) Database {
 | 
				
			||||||
	// Open the database
 | 
						// Open the database
 | 
				
			||||||
| 
						 | 
					@ -111,6 +125,24 @@ func DbConnect(dbpath string) Database {
 | 
				
			||||||
	return &Db{db}
 | 
						return &Db{db}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (d *Db) ReportStatistics(username string, projectName string) (*types.Statistics, error) {
 | 
				
			||||||
 | 
						var result types.Statistics
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := d.Get(&result, reportStatistics, username, projectName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						serialized, err := json.Marshal(result)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Info(string(serialized))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &result, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (d *Db) CheckUser(username string, password string) bool {
 | 
					func (d *Db) CheckUser(username string, password string) bool {
 | 
				
			||||||
	var dbPassword string
 | 
						var dbPassword string
 | 
				
			||||||
	err := d.Get(&dbPassword, "SELECT password FROM users WHERE username = ?", username)
 | 
						err := d.Get(&dbPassword, "SELECT password FROM users WHERE username = ?", username)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										50
									
								
								backend/internal/handlers/reports/Statistics.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								backend/internal/handlers/reports/Statistics.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					package reports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						db "ttime/internal/database"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gofiber/fiber/v2"
 | 
				
			||||||
 | 
						"github.com/gofiber/fiber/v2/log"
 | 
				
			||||||
 | 
						"github.com/golang-jwt/jwt/v5"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetStatistics(c *fiber.Ctx) error {
 | 
				
			||||||
 | 
						// Extract the necessary parameters from the token
 | 
				
			||||||
 | 
						user := c.Locals("user").(*jwt.Token)
 | 
				
			||||||
 | 
						claims := user.Claims.(jwt.MapClaims)
 | 
				
			||||||
 | 
						username := claims["name"].(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Extract project name from query parameters
 | 
				
			||||||
 | 
						projectName := c.Query("projectName")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Info(username, " trying to get statistics for project: ", projectName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if projectName == "" {
 | 
				
			||||||
 | 
							log.Info("Missing project name")
 | 
				
			||||||
 | 
							return c.Status(400).SendString("Missing project name")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If the user is not a project manager, they can't view statistics
 | 
				
			||||||
 | 
						pm, err := db.GetDb(c).IsProjectManager(username, projectName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Info("Error checking if user is project manager:", err)
 | 
				
			||||||
 | 
							return c.Status(500).SendString(err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !pm {
 | 
				
			||||||
 | 
							log.Info("Unauthorized access")
 | 
				
			||||||
 | 
							return c.Status(403).SendString("Unauthorized access")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Retrieve statistics for the project from the database
 | 
				
			||||||
 | 
						statistics, err := db.GetDb(c).ReportStatistics(username, projectName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("Error getting statistics for project:", projectName, ":", err)
 | 
				
			||||||
 | 
							return c.Status(500).SendString(err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Info("Returning statistics")
 | 
				
			||||||
 | 
						// Return the retrieved statistics
 | 
				
			||||||
 | 
						return c.JSON(statistics)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -66,6 +66,15 @@ type WeeklyReport struct {
 | 
				
			||||||
	SignedBy *int `json:"signedBy" db:"signed_by"`
 | 
						SignedBy *int `json:"signedBy" db:"signed_by"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Statistics struct {
 | 
				
			||||||
 | 
						TotalDevelopmentTime int `json:"totalDevelopmentTime" db:"total_development_time"`
 | 
				
			||||||
 | 
						TotalMeetingTime     int `json:"totalMeetingTime" db:"total_meeting_time"`
 | 
				
			||||||
 | 
						TotalAdminTime       int `json:"totalAdminTime" db:"total_admin_time"`
 | 
				
			||||||
 | 
						TotalOwnWorkTime     int `json:"totalOwnWorkTime" db:"total_own_work_time"`
 | 
				
			||||||
 | 
						TotalStudyTime       int `json:"totalStudyTime" db:"total_study_time"`
 | 
				
			||||||
 | 
						TotalTestingTime     int `json:"totalTestingTime" db:"total_testing_time"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type UpdateWeeklyReport struct {
 | 
					type UpdateWeeklyReport struct {
 | 
				
			||||||
	// The name of the project, as it appears in the database
 | 
						// The name of the project, as it appears in the database
 | 
				
			||||||
	ProjectName string `json:"projectName"`
 | 
						ProjectName string `json:"projectName"`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,12 +126,12 @@ func main() {
 | 
				
			||||||
	api.Delete("/removeProject/:projectName", projects.RemoveProject)
 | 
						api.Delete("/removeProject/:projectName", projects.RemoveProject)
 | 
				
			||||||
	api.Delete("/project/:projectID", projects.DeleteProject)
 | 
						api.Delete("/project/:projectID", projects.DeleteProject)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// All report related routes
 | 
						// All report related routes
 | 
				
			||||||
	// reportGroup := api.Group("/report") // Not currently in use
 | 
						// reportGroup := api.Group("/report") // Not currently in use
 | 
				
			||||||
	api.Get("/getWeeklyReport", reports.GetWeeklyReport)
 | 
						api.Get("/getWeeklyReport", reports.GetWeeklyReport)
 | 
				
			||||||
	api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
 | 
						api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
 | 
				
			||||||
	api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports)
 | 
						api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports)
 | 
				
			||||||
 | 
						api.Get("/getStatistics", reports.GetStatistics)
 | 
				
			||||||
	api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
 | 
						api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
 | 
				
			||||||
	api.Put("/signReport/:reportId", reports.SignReport)
 | 
						api.Put("/signReport/:reportId", reports.SignReport)
 | 
				
			||||||
	api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
 | 
						api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import {
 | 
				
			||||||
  NewProject,
 | 
					  NewProject,
 | 
				
			||||||
  WeeklyReport,
 | 
					  WeeklyReport,
 | 
				
			||||||
  StrNameChange,
 | 
					  StrNameChange,
 | 
				
			||||||
 | 
					  Statistics,
 | 
				
			||||||
} from "../Types/goTypes";
 | 
					} from "../Types/goTypes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -258,6 +259,17 @@ interface API {
 | 
				
			||||||
    reportId: number,
 | 
					    reportId: number,
 | 
				
			||||||
    token: string,
 | 
					    token: string,
 | 
				
			||||||
  ): Promise<APIResponse<string>>;
 | 
					  ): Promise<APIResponse<string>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Retrieves the total time spent on a project for a particular user (the user is determined by the token)
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @param {string} projectName The name of the project
 | 
				
			||||||
 | 
					   * @param {string} token The authentication token
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  getStatistics(
 | 
				
			||||||
 | 
					    projectName: string,
 | 
				
			||||||
 | 
					    token: string,
 | 
				
			||||||
 | 
					  ): Promise<APIResponse<Statistics>>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** An instance of the API */
 | 
					/** An instance of the API */
 | 
				
			||||||
| 
						 | 
					@ -962,4 +974,30 @@ export const api: API = {
 | 
				
			||||||
      return { success: false, message: "Failed to delete report" };
 | 
					      return { success: false, message: "Failed to delete report" };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  async getStatistics(
 | 
				
			||||||
 | 
					    token: string,
 | 
				
			||||||
 | 
					    projectName: string,
 | 
				
			||||||
 | 
					  ): Promise<APIResponse<Statistics>> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const response = await fetch(
 | 
				
			||||||
 | 
					        `/api/getStatistics/?projectName=${projectName}`,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          method: "GET",
 | 
				
			||||||
 | 
					          headers: {
 | 
				
			||||||
 | 
					            "Content-Type": "application/json",
 | 
				
			||||||
 | 
					            Authorization: "Bearer " + token,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!response.ok) {
 | 
				
			||||||
 | 
					        return { success: false, message: "Failed to get statistics" };
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        const data = (await response.json()) as Statistics;
 | 
				
			||||||
 | 
					        return { success: true, data };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      return { success: false, message: "Failed to get statistics" };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,6 +124,14 @@ export interface WeeklyReport {
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  signedBy?: number /* int */;
 | 
					  signedBy?: number /* int */;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					export interface Statistics {
 | 
				
			||||||
 | 
					  totalDevelopmentTime: number /* int */;
 | 
				
			||||||
 | 
					  totalMeetingTime: number /* int */;
 | 
				
			||||||
 | 
					  totalAdminTime: number /* int */;
 | 
				
			||||||
 | 
					  totalOwnWorkTime: number /* int */;
 | 
				
			||||||
 | 
					  totalStudyTime: number /* int */;
 | 
				
			||||||
 | 
					  totalTestingTime: number /* int */;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
export interface UpdateWeeklyReport {
 | 
					export interface UpdateWeeklyReport {
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * The name of the project, as it appears in the database
 | 
					   * The name of the project, as it appears in the database
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,7 @@ removeProjectPath = base_url + "/api/removeProject"
 | 
				
			||||||
promoteToPmPath = base_url + "/api/promoteToPm"
 | 
					promoteToPmPath = base_url + "/api/promoteToPm"
 | 
				
			||||||
unsignReportPath = base_url + "/api/unsignReport"
 | 
					unsignReportPath = base_url + "/api/unsignReport"
 | 
				
			||||||
deleteReportPath = base_url + "/api/deleteReport"
 | 
					deleteReportPath = base_url + "/api/deleteReport"
 | 
				
			||||||
 | 
					getStatisticsPath = base_url + "/api/getStatistics"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
debug_output = False
 | 
					debug_output = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -162,3 +163,11 @@ def deleteReport(report_id: int):
 | 
				
			||||||
    return requests.delete(
 | 
					    return requests.delete(
 | 
				
			||||||
        deleteReportPath + "/" + str(report_id),
 | 
					        deleteReportPath + "/" + str(report_id),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def getStatistics(token: string, projectName: string):
 | 
				
			||||||
 | 
					    response = requests.get(
 | 
				
			||||||
 | 
					        getStatisticsPath,
 | 
				
			||||||
 | 
					        headers = {"Authorization": "Bearer " + token},
 | 
				
			||||||
 | 
					        params={"projectName": projectName}
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    return response.json()
 | 
				
			||||||
| 
						 | 
					@ -625,6 +625,46 @@ def test_delete_report():
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    gprint("test_delete_report successful")
 | 
					    gprint("test_delete_report successful")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_get_statistics():
 | 
				
			||||||
 | 
					    # Create admin
 | 
				
			||||||
 | 
					    admin_username = randomString()
 | 
				
			||||||
 | 
					    admin_password = randomString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    project_name = "project" + randomString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    token = register_and_login(admin_username, admin_password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    response = create_project(token, project_name)
 | 
				
			||||||
 | 
					    assert response.status_code == 200, "Create project failed"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    response = submitReport(token, {
 | 
				
			||||||
 | 
					        "projectName": project_name,
 | 
				
			||||||
 | 
					        "week": 1,
 | 
				
			||||||
 | 
					        "developmentTime": 10,
 | 
				
			||||||
 | 
					        "meetingTime": 5,
 | 
				
			||||||
 | 
					        "adminTime": 5,
 | 
				
			||||||
 | 
					        "ownWorkTime": 10,
 | 
				
			||||||
 | 
					        "studyTime": 10,
 | 
				
			||||||
 | 
					        "testingTime": 10,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    response = submitReport(token, {
 | 
				
			||||||
 | 
					        "projectName": project_name,
 | 
				
			||||||
 | 
					        "week": 2,
 | 
				
			||||||
 | 
					        "developmentTime": 10,
 | 
				
			||||||
 | 
					        "meetingTime": 5,
 | 
				
			||||||
 | 
					        "adminTime": 5,
 | 
				
			||||||
 | 
					        "ownWorkTime": 10,
 | 
				
			||||||
 | 
					        "studyTime": 10,
 | 
				
			||||||
 | 
					        "testingTime": 10,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert response.status_code == 200, "Submit report failed"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stats = getStatistics(token, project_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert stats["totalDevelopmentTime"] == 20, "Total development time is not correct"
 | 
				
			||||||
 | 
					    gprint("test_get_statistics successful")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
| 
						 | 
					@ -650,3 +690,4 @@ if __name__ == "__main__":
 | 
				
			||||||
    test_change_user_name()
 | 
					    test_change_user_name()
 | 
				
			||||||
    test_update_weekly_report()
 | 
					    test_update_weekly_report()
 | 
				
			||||||
    test_get_other_users_report_as_pm()
 | 
					    test_get_other_users_report_as_pm()
 | 
				
			||||||
 | 
					    test_get_statistics()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue