diff --git a/backend/internal/api/dto/project_dto.go b/backend/internal/api/dto/project_dto.go index 6ff6f58..f410b19 100644 --- a/backend/internal/api/dto/project_dto.go +++ b/backend/internal/api/dto/project_dto.go @@ -12,7 +12,7 @@ type ProjectDto struct { UpdatedAt time.Time `json:"updatedAt" example:"2024-01-01T00:00:00Z"` LastEditorID string `json:"lastEditorID" example:"01HGW2BBG0000000000000000"` Name string `json:"name" example:"Time Tracking App"` - CustomerID *string `json:"customerId" example:"01HGW2BBG0000000000000000"` + CustomerID *string `json:"customerId,omitempty" example:"01HGW2BBG0000000000000000"` } type ProjectCreateDto struct { diff --git a/backend/internal/api/handlers/project_handler.go b/backend/internal/api/handlers/project_handler.go index 9eeaf94..33d1759 100644 --- a/backend/internal/api/handlers/project_handler.go +++ b/backend/internal/api/handlers/project_handler.go @@ -148,13 +148,17 @@ func (h *ProjectHandler) DeleteProject(c *gin.Context) { // Helper functions for DTO conversion func convertProjectToDTO(project *models.Project) dto.ProjectDto { - customerId := project.CustomerID.String() + var customerIdPtr *string + if project.CustomerID != nil { + customerIdStr := project.CustomerID.String() + customerIdPtr = &customerIdStr + } return dto.ProjectDto{ ID: project.ID.String(), CreatedAt: project.CreatedAt, UpdatedAt: project.UpdatedAt, Name: project.Name, - CustomerID: &customerId, + CustomerID: customerIdPtr, } } @@ -182,13 +186,13 @@ func convertUpdateProjectDTOToModel(dto dto.ProjectUpdateDto, id types.ULID) (mo if dto.CustomerID.Valid { if dto.CustomerID.Value == nil { - update.CustomerID = nil + update.CustomerID = types.Null[types.ULID]() } else { customerID, err := types.ULIDFromString(*dto.CustomerID.Value) if err != nil { return models.ProjectUpdate{}, fmt.Errorf("invalid customer ID: %w", err) } - update.CustomerID = &customerID + update.CustomerID = types.NewNullable(customerID) } } diff --git a/backend/internal/db/db.go b/backend/internal/db/db.go index 78b17d9..23dd847 100644 --- a/backend/internal/db/db.go +++ b/backend/internal/db/db.go @@ -80,7 +80,7 @@ func MigrateDB() error { // createConnection creates a new database connection with the given configuration func createConnection(dbConfig config.DatabaseConfig, dbName string) (*gorm.DB, error) { // Create DSN (Data Source Name) - dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbName=%s sslmode=%s", + dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", dbConfig.Host, dbConfig.Port, dbConfig.User, dbConfig.Password, dbName, dbConfig.SSLMode) // Configure GORM logger diff --git a/backend/internal/models/db.go b/backend/internal/models/db.go index 2aca1c7..aa9bb72 100644 --- a/backend/internal/models/db.go +++ b/backend/internal/models/db.go @@ -90,7 +90,7 @@ func UpdateModel(ctx context.Context, model any, updates any) error { updateMap := make(map[string]any) // Iterate through all fields - for i := 0; i < updateValue.NumField(); i++ { + for i := range updateValue.NumField() { field := updateValue.Field(i) fieldType := updateType.Field(i) diff --git a/backend/internal/models/project.go b/backend/internal/models/project.go index d90536f..96d2990 100644 --- a/backend/internal/models/project.go +++ b/backend/internal/models/project.go @@ -14,7 +14,7 @@ import ( type Project struct { EntityBase Name string `gorm:"column:name;not null"` - CustomerID *types.ULID `gorm:"column:customer_id;type:bytea;not null"` + CustomerID *types.ULID `gorm:"column:customer_id;type:bytea;index"` // Relationships (for Eager Loading) Customer *Customer `gorm:"foreignKey:CustomerID"` @@ -33,9 +33,9 @@ type ProjectCreate struct { // ProjectUpdate contains the updatable fields of a project type ProjectUpdate struct { - ID types.ULID `gorm:"-"` // Exclude from updates - Name *string `gorm:"column:name"` - CustomerID *types.ULID `gorm:"column:customer_id"` + ID types.ULID `gorm:"-"` // Exclude from updates + Name *string `gorm:"column:name"` + CustomerID types.Nullable[types.ULID] `gorm:"column:customer_id"` } // Validate checks if the Create struct contains valid data @@ -44,7 +44,7 @@ func (pc *ProjectCreate) Validate() error { return errors.New("project name cannot be empty") } // Check for valid CustomerID - if pc.CustomerID.Compare(types.ULID{}) == 0 { + if pc.CustomerID != nil && pc.CustomerID.Compare(types.ULID{}) == 0 { return errors.New("customerID cannot be empty") } return nil @@ -122,7 +122,7 @@ func CreateProject(ctx context.Context, create ProjectCreate) (*Project, error) } // Check if the customer exists - if create.CustomerID == nil { + if create.CustomerID != nil { customer, err := GetCustomerByID(ctx, *create.CustomerID) if err != nil { return nil, fmt.Errorf("error checking the customer: %w", err) @@ -160,13 +160,18 @@ func UpdateProject(ctx context.Context, update ProjectUpdate) (*Project, error) } // If CustomerID is updated, check if the customer exists - if update.CustomerID != nil { - customer, err := GetCustomerByID(ctx, *update.CustomerID) - if err != nil { - return nil, fmt.Errorf("error checking the customer: %w", err) - } - if customer == nil { - return nil, errors.New("the specified customer does not exist") + if update.CustomerID.Valid { + if update.CustomerID.Value != nil { + customer, err := GetCustomerByID(ctx, *update.CustomerID.Value) + if err != nil { + return nil, fmt.Errorf("error checking the customer: %w", err) + } + if customer == nil { + return nil, errors.New("the specified customer does not exist") + } + } else { + // If CustomerID is nil, set it to nil in the project + project.CustomerID = nil } } diff --git a/backend/internal/models/user.go b/backend/internal/models/user.go index 3a079d6..51616f5 100644 --- a/backend/internal/models/user.go +++ b/backend/internal/models/user.go @@ -187,7 +187,7 @@ func (uc *UserCreate) Validate() error { } } - if uc.CompanyID.Compare(types.ULID{}) == 0 { + if uc.CompanyID != nil && uc.CompanyID.Compare(types.ULID{}) == 0 { return errors.New("companyID cannot be empty") } @@ -363,13 +363,15 @@ func CreateUser(ctx context.Context, create UserCreate) (*User, error) { return errors.New("email is already in use") } - // Check if company exists - var companyCount int64 - if err := tx.Model(&Company{}).Where("id = ?", create.CompanyID).Count(&companyCount).Error; err != nil { - return fmt.Errorf("error checking company: %w", err) - } - if companyCount == 0 { - return errors.New("the specified company does not exist") + if create.CompanyID != nil { + // Check if company exists + var companyCount int64 + if err := tx.Model(&Company{}).Where("id = ?", create.CompanyID).Count(&companyCount).Error; err != nil { + return fmt.Errorf("error checking company: %w", err) + } + if companyCount == 0 { + return errors.New("the specified company does not exist") + } } // Hash password with unique salt diff --git a/backend/postman/activity.postman_collection.json b/backend/postman/activity.postman_collection.json new file mode 100644 index 0000000..d88b8e2 --- /dev/null +++ b/backend/postman/activity.postman_collection.json @@ -0,0 +1,168 @@ +{ + "info": { + "_postman_id": "YOUR_POSTMAN_ID", + "name": "Activity API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "GET /api/activities", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/activities", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "activities" + ] + } + }, + "response": [] + }, + { + "name": "GET /api/activities/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/activities/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "activities", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "POST /api/activities", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"\",\n\t\"billingRate\": 0\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/activities", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "activities" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/activities/:id", + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"\",\n\t\"billingRate\": 0\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/activities/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "activities", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/activities/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/activities/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "activities", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/backend/postman/company.postman_collection.json b/backend/postman/company.postman_collection.json new file mode 100644 index 0000000..c24bc65 --- /dev/null +++ b/backend/postman/company.postman_collection.json @@ -0,0 +1,168 @@ +{ + "info": { + "_postman_id": "YOUR_POSTMAN_ID", + "name": "Company API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "GET /api/companies", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/companies", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "companies" + ] + } + }, + "response": [] + }, + { + "name": "GET /api/companies/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/companies/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "companies", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "POST /api/companies", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/companies", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "companies" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/companies/:id", + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/companies/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "companies", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/companies/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/companies/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "companies", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/backend/postman/customer.postman_collection.json b/backend/postman/customer.postman_collection.json new file mode 100644 index 0000000..47f00b1 --- /dev/null +++ b/backend/postman/customer.postman_collection.json @@ -0,0 +1,200 @@ +{ + "info": { + "_postman_id": "YOUR_POSTMAN_ID", + "name": "Customer API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "GET /api/customers", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/customers", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "customers" + ] + } + }, + "response": [] + }, + { + "name": "GET /api/customers/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/customers/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "customers", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "GET /api/customers/company/:companyId", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/customers/company/:companyId", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "customers", + "company", + ":companyId" + ], + "variable": [ + { + "key": "companyId", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "POST /api/customers", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"\",\n\t\"companyId\": \"\",\n\t\"ownerUserID\": \"\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/customers", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "customers" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/customers/:id", + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"\",\n\t\"companyId\": \"\",\n\t\"ownerUserID\": \"\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/customers/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "customers", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/customers/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/customers/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "customers", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/backend/postman/project.postman_collection.json b/backend/postman/project.postman_collection.json new file mode 100644 index 0000000..f402a65 --- /dev/null +++ b/backend/postman/project.postman_collection.json @@ -0,0 +1,225 @@ +{ + "info": { + "_postman_id": "YOUR_POSTMAN_ID", + "name": "Project API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "GET /api/projects", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/projects", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "projects" + ] + } + }, + "response": [] + }, + { + "name": "GET /api/projects/with-customers", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/projects/with-customers", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "projects", + "with-customers" + ] + } + }, + "response": [] + }, + { + "name": "GET /api/projects/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/projects/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "projects", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "GET /api/projects/customer/:customerId", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/projects/customer/:customerId", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "projects", + "customer", + ":customerId" + ], + "variable": [ + { + "key": "customerId", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "POST /api/projects", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"\",\n\t\"customerId\": \"\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/projects", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "projects" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/projects/:id", + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"name\": \"\",\n\t\"customerId\": \"\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/projects/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "projects", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/projects/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/projects/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "projects", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/backend/postman/timeentry.postman_collection.json b/backend/postman/timeentry.postman_collection.json new file mode 100644 index 0000000..3963302 --- /dev/null +++ b/backend/postman/timeentry.postman_collection.json @@ -0,0 +1,292 @@ +{ + "info": { + "_postman_id": "YOUR_POSTMAN_ID", + "name": "TimeEntry API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "GET /api/time-entries", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/time-entries", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "time-entries" + ] + } + }, + "response": [] + }, + { + "name": "GET /api/time-entries/me", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/time-entries/me", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "time-entries", + "me" + ] + } + }, + "response": [] + }, + { + "name": "GET /api/time-entries/range", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/time-entries/range?start=2023-01-01T00:00:00Z&end=2023-01-02T00:00:00Z", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "time-entries", + "range" + ], + "query": [ + { + "key": "start", + "value": "2023-01-01T00:00:00Z" + }, + { + "key": "end", + "value": "2023-01-02T00:00:00Z" + } + ] + } + }, + "response": [] + }, + { + "name": "GET /api/time-entries/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/time-entries/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "time-entries", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "GET /api/time-entries/user/:userId", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/time-entries/user/:userId", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "time-entries", + "user", + ":userId" + ], + "variable": [ + { + "key": "userId", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "GET /api/time-entries/project/:projectId", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/time-entries/project/:projectId", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "time-entries", + "project", + ":projectId" + ], + "variable": [ + { + "key": "projectId", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "POST /api/time-entries", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"userID\": \"\",\n \"projectID\": \"\",\n \"activityID\": \"\",\n \"start\": \"2023-01-01T00:00:00Z\",\n \"end\": \"2023-01-01T01:00:00Z\",\n \"description\": \"\",\n \"billable\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/time-entries", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "time-entries" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/time-entries/:id", + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"userID\": \"\",\n \"projectID\": \"\",\n \"activityID\": \"\",\n \"start\": \"2023-01-01T00:00:00Z\",\n \"end\": \"2023-01-01T01:00:00Z\",\n \"description\": \"\",\n \"billable\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/time-entries/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "time-entries", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/time-entries/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/time-entries/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "time-entries", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "" + } + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/backend/postman/user.postman_collection.json b/backend/postman/user.postman_collection.json new file mode 100644 index 0000000..6107a44 --- /dev/null +++ b/backend/postman/user.postman_collection.json @@ -0,0 +1,241 @@ +{ + "info": { + "_postman_id": "YOUR_POSTMAN_ID", + "name": "User API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Auth", + "item": [ + { + "name": "POST /api/auth/login", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\t\"email\": \"\",\n\t\"password\": \"\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/auth/login", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/auth/register", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\t\"email\": \"\",\n\t\"password\": \"\",\n\t\"role\": \"user\",\n\t\"companyID\": \"\",\n\t\"hourlyRate\": 0\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/auth/register", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "auth", + "register" + ] + } + }, + "response": [] + }, + { + "name": "GET /api/auth/me", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/auth/me", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "auth", + "me" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "GET /api/users", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/users", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "users" + ] + } + }, + "response": [] + }, + { + "name": "GET /api/users/:id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/users/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "users", + ":id" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/users", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"email\": \"\",\n\t\"password\": \"\",\n\t\"role\": \"user\",\n\t\"companyID\": \"\",\n\t\"hourlyRate\": 0\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/users", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "users" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/users/:id", + "request": { + "method": "PUT", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"email\": \"\",\n\t\"password\": \"\",\n\t\"role\": \"user\",\n\t\"companyID\": \"\",\n\t\"hourlyRate\": 0\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{API_URL}}/api/users/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "users", + ":id" + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/users/:id", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{JWT_TOKEN}}", + "type": "text" + } + ], + "url": { + "raw": "{{API_URL}}/api/users/:id", + "host": [ + "{{API_URL}}" + ], + "path": [ + "api", + "users", + ":id" + ] + } + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..6ff8eee --- /dev/null +++ b/flake.nix @@ -0,0 +1,113 @@ +{ + description = "Development environment for Go and Next.js (TypeScript)"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + config = { + allowUnfree = true; + }; + }; + + # Go development tools + goPackages = with pkgs; [ + go + gopls + golangci-lint + delve + go-outline + gotools + go-mockgen + gomodifytags + impl + gotests + ]; + + # TypeScript/Next.js development tools + nodePackages = with pkgs; [ + nodejs_20 + nodePackages.typescript + nodePackages.typescript-language-server + nodePackages.yarn + nodePackages.pnpm + nodePackages.npm + nodePackages.prettier + nodePackages.eslint + nodePackages.next + ]; + + # General development tools + commonPackages = with pkgs; [ + git + gh + nixpkgs-fmt + pre-commit + ripgrep + jq + curl + coreutils + gnumake + ]; + + # VSCode with extensions + vscodeWithExtensions = pkgs.vscode-with-extensions.override { + vscodeExtensions = with pkgs.vscode-extensions; [ + golang.go # Go support + esbenp.prettier-vscode # Prettier + dbaeumer.vscode-eslint # ESLint + ms-vscode.vscode-typescript-tslint-plugin # TypeScript + bradlc.vscode-tailwindcss # Tailwind CSS support + jnoortheen.nix-ide # Nix support + ] ++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [ + { + name = "nextjs"; + publisher = "pulkitgangwar"; + version = "1.0.6"; + sha256 = "sha256-L6ZgqNkM0qzSiTKiGfgQB9m3U0HmwLA3NZ9nrslQjeg="; + } + ]; + }; + + in + { + devShells.default = pkgs.mkShell { + buildInputs = goPackages ++ nodePackages ++ commonPackages ++ [ vscodeWithExtensions ]; + + shellHook = '' + echo "🚀 Welcome to the Go and Next.js (TypeScript) development environment!" + echo "📦 Available tools:" + echo " Go: $(go version)" + echo " Node: $(node --version)" + echo " TypeScript: $(tsc --version)" + echo " Next.js: $(npx next --version)" + echo "" + echo "🔧 Use 'code .' to open VSCode with the appropriate extensions" + echo "🔄 Run 'nix flake update' to update dependencies" + ''; + + # Environment variables + GOROOT = "${pkgs.go}/share/go"; + GOPATH = "$(pwd)/.go"; + GO111MODULE = "on"; + + # NodeJS setup + NODE_OPTIONS = "--max-old-space-size=4096"; + }; + + # Optional: Add custom packages if needed + packages = { + # Example of a custom package or script if needed + # my-tool = ... + }; + + # Default package if someone runs `nix build` + defaultPackage = self.devShells.${system}.default; + }); +} \ No newline at end of file