Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 56 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Go Tests

on: [ pull_request ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22.6'

- name: Start PostgreSQL
run: |
docker rm -f gecko-postgres-test > /dev/null 2>&1 || true

docker run -d \
--name gecko-postgres-test \
-p 8081:5432 \
-e POSTGRES_PASSWORD=your_strong_password \
-e POSTGRES_USER=postgres \
postgres:10.4 > /dev/null

# Wait for PostgreSQL to be ready
for i in {1..30}; do
if docker exec gecko-postgres-test pg_isready -U postgres -h localhost; then
break
fi
sleep 1
done

# Create database and setup schema
- name: Setup Database
run: |
# Create the test database
docker exec gecko-postgres-test psql -U postgres -c "CREATE DATABASE testdb;"

# Create the table
docker exec gecko-postgres-test psql -U postgres -d testdb -c "
CREATE TABLE IF NOT EXISTS documents (
name VARCHAR(255) PRIMARY KEY,
content JSONB
);"
env:
PGPASSWORD: your_strong_password

- name: Start Application
run: |
make
./bin/gecko -db "postgresql://postgres:your_strong_password@localhost:8081/testdb?sslmode=disable" -port 8080 &
go test -v ./...
3 changes: 0 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ ENV PATH="/go/bin:${PATH}"

WORKDIR $GOPATH/src/github.com/ACED-IDP/gecko/



COPY go.mod .
COPY go.sum .

RUN go mod download

COPY . .
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ _default: bin/gecko

bin/gecko: gecko/*.go # help: run the server
go build -o bin/gecko

clean:
rm -f bin/gecko
18 changes: 6 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
# gecko

gecko is a configuration server used for fetching inserting user specified configurations that are set during etl jobs and read from the frontend.

## helm cluster setup

Helm setup should be much simpler since env vars are defined.

```
./init_cluster_pg.sh
go build -o bin/gecko && ./bin/gecko
go test -v ./...
```
gecko is a configuration server used for fetching inserting user specified configurations that are set during etl jobs or frontend actions.

## local setup

Expand All @@ -19,6 +9,10 @@ Make sure the below command matches whatever was specified in the init db script
```
./init_postgres.sh
go build -o bin/gecko
./bin/gecko -db "postgresql://postgres:your_strong_password@localhost:5432/testdb?sslmode=disable"
./bin/gecko -db "postgresql://postgres:your_strong_password@localhost:5432/testdb?sslmode=disable" -port 8080
go test -v ./...
```

## helm cluster setup

See helm charts for cluster setup.
81 changes: 63 additions & 18 deletions gecko/config/explorerConfig.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,97 @@
package config

type FieldConfig struct {
Field string `json:"field"`
DataField string `json:"dataField"`
Index string `json:"index"`
Field string `json:"field,omitempty"`
DataField string `json:"dataField,omitempty"`
Index string `json:"index,omitempty"`
Label string `json:"label"`
Type string `json:"type"`
Type string `json:"type,omitempty"`
}

type FilterTab struct {
Title string `json:"title"`
Title string `json:"title,omitempty"`
Fields []string `json:"fields"`
FieldsConfig map[string]FieldConfig `json:"fieldsConfig"`
FieldsConfig map[string]FieldConfig `json:"fieldsConfig,omitempty"`
}

type FiltersConfig struct {
Tabs []FilterTab `json:"tabs"`
}

type TableConfig struct {
Enabled bool `json:"enabled"`
Fields []string `json:"fields"`
Columns map[string]TableColumnsConfig `json:"columns"`
Enabled bool `json:"enabled"`
Fields []string `json:"fields"`
Columns map[string]TableColumnsConfig `json:"columns,omitempty"`
DetailsConfig TableDetailsConfig `json:"detailsConfig,omitempty"`
}

type TableColumnsConfig struct {
Field string `json:"field"`
Title string `json:"title"`
}

type TableDetailsConfig struct {
Panel string `json:"panel,omitempty"`
Mode string `json:"mode,omitempty"`
IDField string `json:"idField,omitempty"`
FilterField string `json:"filterField,omitempty"`
Title string `json:"title,omitempty"`
NodeType string `json:"nodeType,omitempty"`
NodeFields map[string]string `json:"nodeFields,omitempty"`
}

type GuppyConfig struct {
DataType string `json:"dataType"`
NodeCountTitle string `json:"nodeCountTitle"`
FieldMapping []string `json:"fieldMapping"`
DataType string `json:"dataType"`
NodeCountTitle string `json:"nodeCountTitle"`
FieldMapping []GuppyFieldMapping `json:"fieldMapping,omitempty"`
AccessibleFieldCheckList []string `json:"accessibleFieldCheckList,omitempty"`
AccessibleValidationField string `json:"accessibleValidationField,omitempty"`
ManifestMapping ManifestMapping `json:"manifestMapping,omitempty"`
}

type GuppyFieldMapping struct {
Field string `json:"field,omitempty"`
Name string `json:"name,omitempty"`
}

type ManifestMapping struct {
ResourceIndexType string `json:"resourceIndexType,omitempty"`
ResourceIdField string `json:"resourceIdField,omitempty"`
ReferenceIdFieldInResourceIndex string `json:"referenceIdFieldInResourceIndex,omitempty"`
ReferenceIdFieldInDataIndex string `json:"referenceIdFieldInDataIndex,omitempty"`
}

type Chart struct {
ChartType string `json:"chartType"`
Title string `json:"title"`
}

type ButtonConfig struct {
Enabled bool `json:"enabled,omitempty"`
Type string `json:"type,omitempty"`
Action string `json:"action,omitempty"`
Title string `json:"title,omitempty"`
LeftIcon string `json:"leftIcon,omitempty"`
RightIcon string `json:"rightIcon,omitempty"`
FileName string `json:"fileName,omitempty"`
ActionArgs ButtonActionArgs `json:"actionArgs,omitempty"`
}

type ButtonActionArgs struct {
ResourceIndexType string `json:"resourceIndexType,omitempty"`
ResourceIdField string `json:"resourceIdField,omitempty"`
ReferenceIdFieldInDataIndex string `json:"referenceIdFieldInDataIndex,omitempty"`
ReferenceIdFieldInResourceIndex string `json:"referenceIdFieldInResourceIndex,omitempty"`
FileFields []string `json:"fileFields,omitempty"`
}

type ConfigItem struct {
TabTitle string `json:"tabTitle"`
GuppyConfig GuppyConfig `json:"guppyConfig"`
Charts map[string]Chart `json:"charts"`
Filters FiltersConfig `json:"filters"` // Updated type
Table TableConfig `json:"table"` // Updated type
Dropdowns map[string]any `json:"dropdowns"`
Buttons []any `json:"buttons"`
LoginForDownload bool `json:"loginForDownload"`
Charts map[string]Chart `json:"charts,omitempty"`
Filters FiltersConfig `json:"filters"`
Table TableConfig `json:"table"`
Dropdowns map[string]any `json:"dropdowns,omitempty"`
Buttons []ButtonConfig `json:"buttons,omitempty"`
LoginForDownload bool `json:"loginForDownload,omitempty"`
}
41 changes: 22 additions & 19 deletions gecko/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,16 @@ func (server *Server) Init() (*Server, error) {
if server.logger == nil {
return nil, errors.New("gecko server initialized without logger")
}
server.logger.logger.Printf("DB: %#v, JWTApp: %#v, Logger: %#v", server.db, server.jwtApp, server.logger)
server.logger.Info("DB: %#v, JWTApp: %#v, Logger: %#v", server.db, server.jwtApp, server.logger)
return server, nil
}

func (server *Server) MakeRouter() *iris.Application {
router := iris.New()
if router == nil {
log.Fatal("Failed to initialize router")
server.logger.Error("Failed to initialize router")
}
router.Use(recoveryMiddleware)
router.Get("/", func(ctx iris.Context) {
server.logger.logger.Println("Root handler called")
ctx.JSON(iris.Map{"message": "Hello, World!"})
})
router.OnErrorCode(iris.StatusNotFound, handleNotFound)
router.Get("/health", server.handleHealth)
router.Get("/config/{configId}", server.handleConfigGET)
Expand All @@ -82,17 +78,18 @@ func (server *Server) MakeRouter() *iris.Application {
router.UseRouter(func(ctx iris.Context) {
req := ctx.Request()
if req == nil || req.URL == nil {
log.Println("WARNING: Request or URL is nil")
server.logger.Warning("Request or URL is nil")
ctx.StatusCode(http.StatusInternalServerError)
ctx.WriteString("Internal Server Error")
return
}
req.URL.Path = strings.TrimSuffix(req.URL.Path, "/")
ctx.Next()
})

// Build the router to ensure it's ready for net/http
if err := router.Build(); err != nil {
log.Fatalf("Failed to build Iris router: %v", err)
server.logger.Error("Failed to build Iris router: %v", err)
}
return router
}
Expand Down Expand Up @@ -125,7 +122,7 @@ func (server *Server) handleConfigGET(ctx iris.Context) {
_ = errResponse.write(ctx)
return
}
server.logger.logger.Println(doc)
server.logger.Info("%#v", doc)
_ = jsonResponseFrom(doc, http.StatusOK).write(ctx)
}

Expand All @@ -148,7 +145,7 @@ func (server *Server) handleConfigDELETE(ctx iris.Context) {
}

okmsg := map[string]any{"code": 200, "message": fmt.Sprintf("DELETED: %s", configId)}
server.logger.logger.Println(okmsg)
server.logger.Info("%#v", okmsg)
_ = jsonResponseFrom(okmsg, http.StatusOK).write(ctx)
}

Expand All @@ -157,44 +154,51 @@ func (server *Server) handleConfigPUT(ctx iris.Context) {
data := []config.ConfigItem{}
body, err := ctx.GetBody()
if err != nil {
msg := fmt.Sprintf("client query failed: %s", err.Error())
msg := fmt.Sprintf("GetBody() failed: %s", err.Error())
errResponse := newErrorResponse(msg, 500, nil)
errResponse.log.write(server.logger)
_ = errResponse.write(ctx)
return

}
if !json.Valid(body) {
msg := "Invalid JSON format"
errResponse := newErrorResponse(msg, 400, nil)
errResponse.log.write(server.logger)
_ = errResponse.write(ctx)
return
}
errResponse := unmarshal(body, &data)
if errResponse != nil {
fmt.Printf("HELLO DATA: ERR: %#v\n", errResponse)
msg := fmt.Sprintf("body data unmarshal failed: %s", errResponse.err)
errResponse := newErrorResponse(msg, 400, nil)
errResponse.log.write(server.logger)
_ = errResponse.write(ctx)
return
}
err = configPUT(server.db, configId, data)
if err != nil {
msg := fmt.Sprintf("client query failed: %s", err.Error())
msg := fmt.Sprintf("configPut failed: %s", err.Error())
errResponse := newErrorResponse(msg, 500, nil)
errResponse.log.write(server.logger)
_ = errResponse.write(ctx)
return
}

okmsg := map[string]any{"code": 200, "message": fmt.Sprintf("ACCEPTED: %s", configId)}
server.logger.logger.Println(okmsg)
server.logger.Info("%#v", okmsg)
_ = jsonResponseFrom(okmsg, http.StatusOK).write(ctx)
}

func (server *Server) handleHealth(ctx iris.Context) {
server.logger.logger.Println("Entering handleHealth")
server.logger.Info("Entering handleHealth")
err := server.db.Ping()
if err != nil {
server.logger.logger.Printf("Database ping failed: %v", err)
server.logger.Error("Database ping failed: %v", err)
response := newErrorResponse("database unavailable", 500, nil)
_ = response.write(ctx)
return
}
server.logger.logger.Println("Health check passed")
server.logger.Info("Health check passed")
_ = jsonResponseFrom("Healthy", http.StatusOK).write(ctx)
}

Expand All @@ -220,7 +224,6 @@ func unmarshal(body []byte, x any) *ErrorResponse {
if len(body) == 0 {
return newErrorResponse("empty request body", http.StatusBadRequest, nil)
}

err := json.Unmarshal(body, x)
if err != nil {
structType := reflect.TypeOf(x)
Expand Down
11 changes: 0 additions & 11 deletions init_cluster_pg.sh

This file was deleted.

Loading