Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c88e307
feat(api): complete CRUD endpoints for entities and add new resource …
shark0F0497 Mar 24, 2026
4f7dbd7
feat(db): add index optimizations for soft-delete filtering and query…
shark0F0497 Mar 24, 2026
b763438
feat(api): add factory count to organization endpoints
shark0F0497 Mar 24, 2026
4e784f3
feat(api): add location, timezone, and settings fields to factory cre…
shark0F0497 Mar 24, 2026
7bf6e32
feat(api): add replace factory endpoint (PUT /factories/:id)
shark0F0497 Mar 24, 2026
01dfd84
refactor(api): standardize update endpoints to use PUT instead of PATCH
shark0F0497 Mar 24, 2026
316d1e0
fix(docs): remove duplicate @Router annotation
shark0F0497 Mar 25, 2026
55dbeb0
feat(db): simplify index naming and structure
shark0F0497 Mar 25, 2026
960c06b
feat(api): enhance field flexibility
shark0F0497 Mar 25, 2026
8fe7170
feat(api): add email field to data collector creation
shark0F0497 Mar 26, 2026
bddcb31
feat(api): add queue size to inspector responses and enhance skill an…
shark0F0497 Mar 26, 2026
33cbe0d
feat(api): enhance factory, scene, skill, and SOP structures with new…
shark0F0497 Mar 29, 2026
45205cf
feat(api): add conflict response for skill deletion when referenced b…
shark0F0497 Mar 29, 2026
90d7bc2
feat(api): enforce deletion constraints for factories,scenes,subscenes
shark0F0497 Mar 29, 2026
a59b747
feat(api):enhance data collector, inspector, robot, and station struc…
shark0F0497 Mar 29, 2026
d02afb7
fix(lint): fix golangci-lint issues
shark0F0497 Mar 30, 2026
7268cf6
fix(api): enforce soft delete constraints across multiple handlers
shark0F0497 Mar 30, 2026
93aa218
feat(api): extract common utility functions for api handlers
shark0F0497 Mar 30, 2026
1665b29
feat(api): improve slug validation and enhance skill metadata handling
shark0F0497 Mar 30, 2026
d01ac13
feat(api): add swaggertype annotations for metadata and other fields …
shark0F0497 Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ Second Release Implementation Order:
- Role-based access control (RBAC)
- API private key management
2. **Scene & Skill Management**
- scene and subscene CRUD
- skill and sop CRUD
- scene and subscene CRUD
- skill and sop CRUD
3. **Order & Task Management**
- order CRUD
- batch CRUD
Expand Down
103 changes: 103 additions & 0 deletions internal/api/handlers/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: 2026 ArcheBase
//
// SPDX-License-Identifier: MulanPSL-2.0

package handlers

import (
"database/sql"
"encoding/json"
"strings"
"time"
)

// maxSlugLength matches VARCHAR(100) for slug columns in the schema.
const maxSlugLength = 100

// invalidSlugUserMessage is returned when slug fails isValidSlug (length or charset).
const invalidSlugUserMessage = "slug must be at most 100 characters and contain only alphanumeric characters and hyphens"

// isValidSlug checks non-empty slug, length <= maxSlugLength, and alphanumeric plus hyphen only.
func isValidSlug(s string) bool {
if len(s) == 0 || len(s) > maxSlugLength {
return false
}
for _, c := range s {
if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '-' {
return false
}
}
return true
}

// parseJSONRaw parses a JSON string and returns it as a raw interface{}.
func parseJSONRaw(s string) interface{} {
s = strings.TrimSpace(s)
if s == "" || s == "null" {
return nil
}
var result interface{}
if err := json.Unmarshal([]byte(s), &result); err != nil {
return s
}
return result
}

func parseJSONArray(s string) []string {
s = strings.TrimSpace(s)
if s == "" || s == "null" {
return nil
}
var result []string
if err := json.Unmarshal([]byte(s), &result); err != nil {
return nil
}
return result
}

// jsonStringOrEmptyObject returns JSON text for sensor_suite/capabilities.
// Empty or null raw defaults to {}.
func jsonStringOrEmptyObject(raw json.RawMessage) string {
ns := sqlNullJSONFromRaw(raw)
if !ns.Valid {
return "{}"
}
return ns.String
}

func sqlNullJSONFromRaw(raw json.RawMessage) sql.NullString {
if len(raw) == 0 {
return sql.NullString{Valid: false}
}
s := strings.TrimSpace(string(raw))
if s == "" || s == "null" {
return sql.NullString{Valid: false}
}
return sql.NullString{String: s, Valid: true}
}

func formatDBTimeToRFC3339(raw string) string {
s := strings.TrimSpace(raw)
if s == "" {
return ""
}

// MySQL commonly returns "YYYY-MM-DD HH:MM:SS" or with fractional seconds.
// Some drivers/configs may return RFC3339.
layouts := []string{
"2006-01-02 15:04:05",
"2006-01-02 15:04:05.999999",
"2006-01-02 15:04:05.999999999",
time.RFC3339Nano,
time.RFC3339,
}

for _, layout := range layouts {
if t, err := time.Parse(layout, s); err == nil {
return t.Format(time.RFC3339)
}
}

// Fallback: return original string instead of a wrong timestamp.
return s
}
Loading
Loading