alex 12407b74e4 Initial commit - Stage 1 working version
Saving current working state before proceeding to Stage 2.
Includes:
- Backend: Python-based QC validator with shapefile processing
- Frontend: Drag-and-drop file upload interface
- Sample files for testing
- Documentation and revision history

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 13:43:57 -07:00

358 lines
11 KiB
Go

package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"verofy-backend/models"
"verofy-backend/qc"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var db *gorm.DB
func getEnv(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
func initDB() {
if err := godotenv.Load(); err != nil {
log.Println("No .env file found")
}
dsn := fmt.Sprintf(
"host=%s user=%s password=%s dbname=%s port=%s sslmode=require",
getEnv("DB_HOST", "localhost"),
getEnv("DB_USER", "postgres"),
getEnv("DB_PASS", ""),
getEnv("DB_NAME", "verofy"),
getEnv("DB_PORT", "5432"),
)
var err error
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
}
// Helper function to parse geometry from json.RawMessage
func parseGeometry(rawGeometry json.RawMessage) interface{} {
if len(rawGeometry) == 0 {
return nil
}
var geometry interface{}
if err := json.Unmarshal(rawGeometry, &geometry); err != nil {
log.Printf("Failed to parse geometry: %v", err)
return nil
}
return geometry
}
func main() {
initDB()
// Define configuration variables
schema := getEnv("SCHEMA_NAME", "eli_test")
segmentTable := getEnv("SEGMENT_TABLE", "segment2")
zoneCol := getEnv("ZONE_COLUMN", "group_1")
mapIDCol := getEnv("MAPID_COLUMN", "mapid")
idCol := getEnv("ID_COLUMN", "id")
qcFlagCol := getEnv("QCFLAG_COLUMN", "qc_flag")
serverPort := getEnv("SERVER_PORT", "8080")
router := gin.Default()
router.Use(cors.Default())
router.Static("/static", "../Frontend")
router.GET("/", func(c *gin.Context) {
c.File("../Frontend/index.html")
})
// Register QC routes
qc.GraphConnectivityRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
qc.SingleSpanRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
qc.SiteConnectivityRoute(router, db, schema)
qc.UndergroundEndpointsRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
qc.AerialEndpointsRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
qc.ZoneContainmentRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
router.GET("/api/markets", func(c *gin.Context) {
var markets []models.MarketOption
table := fmt.Sprintf("%s.map_projects", schema)
db.Table(table).Select("mapid, TRIM(project) as project").Where("mapid IS NOT NULL").Order("project").Scan(&markets)
c.JSON(http.StatusOK, markets)
})
router.GET("/api/zones", func(c *gin.Context) {
mapID := c.Query("map_id")
if mapID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing map_id query parameter"})
return
}
var zones []string
table := fmt.Sprintf("%s.%s", schema, segmentTable)
db.Table(table).Where(fmt.Sprintf("%s = ? AND %s IS NOT NULL", mapIDCol, zoneCol), mapID).Distinct(zoneCol).Pluck(zoneCol, &zones)
c.JSON(http.StatusOK, zones)
})
router.GET("/api/segments", func(c *gin.Context) {
mapID := c.Query("map_id")
zone := c.Query("zone")
var segments []struct {
ID0 int `gorm:"column:id_0"`
MapID int `gorm:"column:mapid"`
SegmentType string `gorm:"column:segment_type"`
SegmentStatus string `gorm:"column:segment_status"`
ID int `gorm:"column:id"`
ProtectionStatus string `gorm:"column:protection_status"`
QCFlag string `gorm:"column:qc_flag"`
Group1 *string `gorm:"column:group_1"`
Geometry json.RawMessage `gorm:"column:geometry"`
}
table := fmt.Sprintf("%s.%s", schema, segmentTable)
query := db.Table(table).Select(fmt.Sprintf("id_0, %s, segment_type, segment_status, %s, protection_status, %s, \"%s\" as group_1, ST_AsGeoJSON(ST_Transform(geom, 4326))::json AS geometry", mapIDCol, idCol, qcFlagCol, zoneCol))
if mapID != "" {
query = query.Where(fmt.Sprintf("%s = ?", mapIDCol), mapID)
}
if zone != "" {
query = query.Where(fmt.Sprintf("\"%s\" = ?", zoneCol), zone)
}
query.Find(&segments)
features := []map[string]interface{}{}
for _, s := range segments {
features = append(features, map[string]interface{}{
"type": "Feature",
"geometry": parseGeometry(s.Geometry),
"properties": map[string]interface{}{
"id_0": s.ID0,
"mapid": s.MapID,
"segment_type": s.SegmentType,
"segment_status": s.SegmentStatus,
"id": s.ID,
"protection_status": s.ProtectionStatus,
"qc_flag": s.QCFlag,
"group_1": s.Group1,
},
})
}
c.JSON(http.StatusOK, map[string]interface{}{
"type": "FeatureCollection",
"features": features,
})
})
// SITES
router.GET("/api/sites", func(c *gin.Context) {
mapID := c.Query("mapprojectid")
var sites []struct {
GID int `gorm:"column:gid"`
ID *int `gorm:"column:id"`
MapProjectID *int `gorm:"column:mapprojectid"`
Name *string `gorm:"column:name"`
Address1 *string `gorm:"column:address"`
City *string `gorm:"column:city"`
State *string `gorm:"column:state"`
Zip *string `gorm:"column:zip"`
Group1 *string `gorm:"column:group1"`
Geometry json.RawMessage `gorm:"column:geometry"`
}
table := fmt.Sprintf("%s.sites", schema)
query := db.Table(table).Select("gid, id, \"MapProjectID\" as mapprojectid, \"Name\" as name, \"Address1\" as address, \"City\" as city, \"State\" as state, \"Zip\" as zip, \"Group 1\" as group1, ST_AsGeoJSON(geometry)::json AS geometry")
if mapID != "" {
query = query.Where("\"MapProjectID\" = ?", mapID)
}
query.Find(&sites)
features := []map[string]interface{}{}
for _, s := range sites {
features = append(features, map[string]interface{}{
"type": "Feature",
"geometry": parseGeometry(s.Geometry),
"properties": map[string]interface{}{
"gid": s.GID,
"id": s.ID,
"mapprojectid": s.MapProjectID,
"name": s.Name,
"address": s.Address1,
"city": s.City,
"state": s.State,
"zip": s.Zip,
"group1": s.Group1,
},
})
}
c.JSON(http.StatusOK, map[string]interface{}{
"type": "FeatureCollection",
"features": features,
})
})
// POLES
router.GET("/api/poles", func(c *gin.Context) {
mapID := c.Query("map_id")
var poles []models.PolesGeoJSON
table := fmt.Sprintf("%s.poles", schema)
query := db.Table(table).Select("gid, id, mapprojectid, name, tags, group1, group2, owner, poleheight, attachmentheight, ST_AsGeoJSON(ST_Transform(geom, 4326))::json AS geometry")
if mapID != "" {
query = query.Where("mapprojectid = ?", mapID)
}
query.Find(&poles)
features := []map[string]interface{}{}
for _, p := range poles {
features = append(features, map[string]interface{}{
"type": "Feature",
"geometry": parseGeometry(p.Geometry),
"properties": map[string]interface{}{
"gid": p.GID,
"id": p.ID,
"mapprojectid": p.MapProjectID,
"name": p.Name,
"tags": p.Tags,
"group1": p.Group1,
"group2": p.Group2,
"owner": p.Owner,
"poleheight": p.PoleHeight,
"attachmentheight": p.AttachmentHeight,
},
})
}
c.JSON(http.StatusOK, map[string]interface{}{
"type": "FeatureCollection",
"features": features,
})
})
// Access_Points
router.GET("/api/access_points", func(c *gin.Context) {
mapID := c.Query("map_id")
var accessPoints []models.AccessPointGeoJSON
table := fmt.Sprintf("%s.access_points", schema)
query := db.Table(table).Select(`
gid, id, name, mapprojectid, latitude, longitude, manufacturer, size, locked, description, aka,
createdby, createddate, modifiedby, modifieddate, historyid, group1, group2, typeid, statusid,
crmvendorid, billdate,
ST_AsGeoJSON(ST_Transform(geom, 4326))::json AS geometry
`)
if mapID != "" {
query = query.Where("mapprojectid = ?", mapID)
}
query.Find(&accessPoints)
features := []map[string]interface{}{}
for _, ap := range accessPoints {
features = append(features, map[string]interface{}{
"type": "Feature",
"geometry": parseGeometry(ap.Geometry),
"properties": map[string]interface{}{
"gid": ap.GID,
"id": ap.ID,
"name": ap.Name,
"mapprojectid": ap.MapProjectID,
"latitude": ap.Latitude,
"longitude": ap.Longitude,
"manufacturer": ap.Manufacturer,
"size": ap.Size,
"locked": ap.Locked,
"description": ap.Description,
"aka": ap.AKA,
"createdby": ap.CreatedBy,
"createddate": ap.CreatedDate,
"modifiedby": ap.ModifiedBy,
"modifieddate": ap.ModifiedDate,
"historyid": ap.HistoryID,
"group1": ap.Group1,
"group2": ap.Group2,
"typeid": ap.TypeID,
"statusid": ap.StatusID,
"crmvendorid": ap.CRMVendorID,
"billdate": ap.BillDate,
},
})
}
c.JSON(http.StatusOK, map[string]interface{}{
"type": "FeatureCollection",
"features": features,
})
})
// Info Objects - FIXED
router.GET("/api/info", func(c *gin.Context) {
mapID := c.Query("map_id")
var infos []models.InfoGeoJSON
table := fmt.Sprintf("%s.info", schema)
query := db.Table(table).Select(`
id, name, tags, description, group_1, group_2,
ST_AsGeoJSON(geom)::json AS geometry
`)
if mapID != "" {
query = query.Where("mapprojectid = ?", mapID)
}
if err := query.Find(&infos).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
features := []map[string]interface{}{}
for _, info := range infos {
features = append(features, map[string]interface{}{
"type": "Feature",
"geometry": parseGeometry(info.Geometry),
"properties": map[string]interface{}{
"id": info.ID,
"name": info.Name,
"tags": info.Tags,
"description": info.Description,
"group_1": info.Group1,
"group_2": info.Group2,
},
})
}
c.JSON(http.StatusOK, map[string]interface{}{
"type": "FeatureCollection",
"features": features,
})
})
// Server Start
log.Printf("Server is running on http://localhost:%s", serverPort)
if err := router.Run(":" + serverPort); err != nil {
log.Fatal("Server failed:", err)
}
}