Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ef73229
switch to fiber/v3, add in project configuration
matthewpeterkort May 19, 2026
5439bf0
update go versions
matthewpeterkort May 19, 2026
b84feef
update makefile
matthewpeterkort May 19, 2026
5fab8b8
update projec/org routing
matthewpeterkort May 19, 2026
92682ce
update middleware
matthewpeterkort May 19, 2026
e86174d
fix build error
matthewpeterkort May 20, 2026
9b74ae9
fix gecko default header size
matthewpeterkort May 20, 2026
d515aa8
add put, delete project ops
matthewpeterkort May 20, 2026
07bc6d6
move stuff around, scaffold support for github project trees
matthewpeterkort May 20, 2026
903f35a
update server routes
matthewpeterkort May 20, 2026
33e2f49
fix build err
matthewpeterkort May 20, 2026
56a3ce3
fix schema bug
matthewpeterkort May 20, 2026
0a856d7
fix ports
matthewpeterkort May 20, 2026
9735d11
update gecko to use fence for constructing gh redirect
matthewpeterkort May 21, 2026
70c4d9c
fix routes
matthewpeterkort May 21, 2026
6de1633
update routes
matthewpeterkort May 21, 2026
b940d61
fix org name mappings
matthewpeterkort May 21, 2026
b7b49c6
update gecko to no longer serve file bits and instead use github API
matthewpeterkort May 22, 2026
54c93fb
no longer pass bytes , instead forward github raw file call
matthewpeterkort May 22, 2026
5e9eb19
add date last modified to tree return fields
matthewpeterkort May 22, 2026
dd2831c
build out the bot PR file upload use case
matthewpeterkort May 22, 2026
849b5eb
fix bugs
matthewpeterkort May 22, 2026
a890ca0
patch explorerConfig structs
matthewpeterkort May 26, 2026
21a16da
fix server errors
matthewpeterkort May 26, 2026
13e3882
fix bug so that token is now write access
matthewpeterkort May 26, 2026
667eccc
remove /tmp repo clone location and add support for real data mountin…
matthewpeterkort May 26, 2026
3c270b7
add some safety code to initialize repo if it does not exist
matthewpeterkort May 26, 2026
29dc2e1
updates
matthewpeterkort May 26, 2026
c06a69b
Add GitHub App pending repository flow
matthewpeterkort May 28, 2026
1f70bba
fix bug
matthewpeterkort May 28, 2026
bd06f6e
fix bug
matthewpeterkort May 28, 2026
3e70b2a
cleanup
matthewpeterkort May 28, 2026
9b9e4d9
iron out frontend
matthewpeterkort May 28, 2026
05e012c
refactor strucure, fix bugs
matthewpeterkort Jun 2, 2026
296e778
reroute alot of syfon calls into gecko from the frontend
matthewpeterkort Jun 2, 2026
48e36f7
Fix Gecko project git state preservation and delete cleanup
matthewpeterkort Jun 5, 2026
fba69ad
add support for user specfic settings pages
matthewpeterkort Jun 5, 2026
5185fbe
reorg gecko to be less messy
matthewpeterkort Jun 5, 2026
63c4e6d
iron out github connections
matthewpeterkort Jun 6, 2026
177cf14
patch gh action
matthewpeterkort Jun 6, 2026
339b5c0
fix bugs
matthewpeterkort Jun 8, 2026
72aea72
update to fallback redirect logic
matthewpeterkort Jun 8, 2026
f73184f
fix url templating
matthewpeterkort Jun 8, 2026
6a5ecb7
fix bug
matthewpeterkort Jun 8, 2026
8a7612a
fix bug
matthewpeterkort Jun 8, 2026
12b9dae
cleanup
matthewpeterkort Jun 8, 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
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.git
.github
bin
coverage.out
node_modules
.tmp
.vscode
.idea
.DS_Store
README.md
.codex
.gocache
.gomodcache
dist
tmp
*_test.go
tests
7 changes: 4 additions & 3 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24.2'
go-version: '1.26.2'

- name: Start PostgreSQL
run: |
Expand Down Expand Up @@ -108,8 +108,9 @@ jobs:
-port 8080 \
-qdrant-api-key "your_qdrant_api_key" \
-qdrant-host localhost \
-qdrant-port 6334 &

-qdrant-port 6334 \
-git-data-dir /tmp/gecko-git &

# Wait for application to be ready
echo "Waiting for application to be ready..."
for i in {1..30}; do
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.DS_Store
bin/gecko
bin/gecko
gecko
46 changes: 31 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
FROM golang:1.24.2-alpine AS build-deps
RUN apk add make git bash build-base libc-dev binutils-gold curl postgresql-client
# syntax=docker/dockerfile:1.7
FROM golang:1.26.3-alpine3.22 AS builder
RUN apk add --no-cache git ca-certificates tzdata

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
ENV GOPATH=/go
ENV PATH="/go/bin:${PATH}"

WORKDIR $GOPATH/src/github.com/calypr/gecko/
WORKDIR /src

COPY go.mod .
COPY go.sum .
RUN go mod download
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go mod download

COPY . .

RUN GITCOMMIT=$(git rev-parse HEAD) \
GITVERSION=$(git describe --always --tags) \
&& go build \
-ldflags="-X 'github.com/calypr/gecko/gecko/version.GitCommit=${GITCOMMIT}' -X 'github.com/calypr/gecko/gecko/version.GitVersion=${GITVERSION}'" \
-o bin/gecko
ARG TARGETOS=linux
ARG TARGETARCH=amd64
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
GOOS="$TARGETOS" GOARCH="$TARGETARCH" \
go build \
-trimpath \
-ldflags="-s -w" \
-o /out/gecko

FROM alpine:3.22
RUN apk add --no-cache ca-certificates tzdata && \
addgroup -S gecko && \
adduser -S -G gecko -h /app gecko

WORKDIR /app

COPY --from=builder /out/gecko /app/gecko
COPY --from=builder /src/docs/swagger.json /app/docs/swagger.json

USER gecko
EXPOSE 8080
ENTRYPOINT ["/app/gecko"]
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
.PHONY: _default clean swagger

_default: bin/gecko
@: # if we have a command this silences "nothing to be done"
@:

bin/gecko: main.go gecko/*.go # help: run the server
# Simply depend on main.go, or leave the prerequisites blank.
# Go handles the rest.
bin/gecko: main.go
go build -o bin/gecko .

clean:
Expand Down
46 changes: 46 additions & 0 deletions apierror/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package apierror

type Type string

const (
TypeUnauthorized Type = "unauthorized"
TypeForbidden Type = "forbidden"
TypeNotFound Type = "not_found"
TypeMethodNotAllowed Type = "method_not_allowed"
TypeInvalidConfigType Type = "invalid_config_type"
TypeConfigNotFound Type = "config_not_found"
TypeInvalidJSON Type = "invalid_json"
TypeEmptyRequestBody Type = "empty_request_body"
TypeInvalidRequestBody Type = "invalid_request_body"
TypeValidationFailed Type = "validation_failed"
TypeMissingAuthorization Type = "missing_authorization"
TypeInvalidAuthorizationResponse Type = "invalid_authorization_response"
TypeInvalidJWTHandler Type = "invalid_jwt_handler"
TypeInvalidProjectID Type = "invalid_project_id"
TypeMissingProjectID Type = "missing_project_id"
TypeProjectIDMismatch Type = "project_id_mismatch"
TypeInvalidDirectory Type = "invalid_directory"
TypeDatabaseError Type = "database_error"
TypeDatabaseUnavailable Type = "database_unavailable"
TypeGraphQueryFailed Type = "graph_query_failed"
TypeInvalidDistance Type = "invalid_distance"
TypeInvalidVectorRequest Type = "invalid_vector_request"
TypeInvalidPointData Type = "invalid_point_data"
TypeMissingIdentifier Type = "missing_identifier"
TypeInvalidUUID Type = "invalid_uuid"
TypeInvalidQueryParameter Type = "invalid_query_parameter"
TypePointNotFound Type = "point_not_found"
TypeVectorCollectionNotFound Type = "vector_collection_not_found"
TypeVectorCollectionAlreadyExists Type = "vector_collection_already_exists"
TypeVectorStoreUnavailable Type = "vector_store_unavailable"
TypeVectorOperationFailed Type = "vector_operation_failed"
TypeAuthorizationServiceError Type = "authorization_service_error"
TypeAppCardNotFound Type = "app_card_not_found"
)

type Error struct {
Type Type `json:"type"`
Message string `json:"message"`
Code int `json:"code"`
Details map[string]any `json:"details,omitempty"`
}
14 changes: 9 additions & 5 deletions gecko/config/configurable.go → config/configurable.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ type Configurable interface {
IsZero() bool
}

// Update config.Config to implement the interface
func (c Config) IsZero() bool {
return len(c.ExplorerConfig) == 0
}

func (ap AppsConfig) IsZero() bool {
return len(ap.AppCards) == 0
}

func (n NavPageLayoutProps) IsZero() bool {
return len(n.HeaderProps.LeftNav) == 0 &&
len(n.FooterProps.RightSection.Columns) == 0 &&
Expand All @@ -24,3 +19,12 @@ func (n NavPageLayoutProps) IsZero() bool {
func (fs FilesummaryConfig) IsZero() bool {
return len(fs.Config) == 0
}

func (p ProjectConfig) IsZero() bool {
return p.Title == "" &&
p.ContactEmail == "" &&
p.SrcRepo == "" &&
p.OrgTitle == "" &&
p.Description == "" &&
p.ProjectTitle == ""
}
File renamed without changes.
166 changes: 166 additions & 0 deletions config/explorerConfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package config

import (
"encoding/json"
"reflect"
"testing"
)

const explorerJSON = `{
"sharedFilters": {
"defined": {
"proj": [
{
"index": "file",
"field": "project_id"
}
]
}
},
"explorerConfig": [
{
"tabTitle": "test",
"guppyConfig": {
"dataType": "file",
"nodeCountTitle": "file Count",
"fieldMapping": [
{
"field": "file_id",
"name": "ID"
}
],
"manifestMapping": {
"resourceIndexType": "file",
"resourceIdField": "file_id"
}
},
"charts": {
"a": {
"chartType": "bar",
"title": "a"
}
},
"filters": {
"tabs": [
{
"title": "Filters",
"fields": [
"project_id"
]
}
]
},
"table": {
"enabled": true,
"fields": [
"project_id"
],
"detailsConfig": {
"panel": "details",
"title": "Details"
}
},
"buttons": [
{
"enabled": true,
"type": "manifest",
"action": "download",
"title": "Download Manifest",
"actionArgs": {
"resourceIndexType": "file"
}
}
],
"preFilters": {
"project_id": ["proj1"]
}
}
],
"fileActions": {
"extensions": {
"tiff": ["file_download", "file_image"],
"tif": ["file_download", "file_image"],
"default": ["file_download"]
},
"actions": {
"file_download": "/user/data/download",
"file_image": "/image-viewer/view"
}
}
}`

func TestExplorerConfig_RoundTrip(t *testing.T) {
var cfg Config
if err := json.Unmarshal([]byte(explorerJSON), &cfg); err != nil {
t.Fatalf("unmarshal failed: %v", err)
}

marshaled, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("marshal failed: %v", err)
}

var originalMap any
if err := json.Unmarshal([]byte(explorerJSON), &originalMap); err != nil {
t.Fatalf("Unmarshal original map failed: %v", err)
}

var marshaledMap any
if err := json.Unmarshal(marshaled, &marshaledMap); err != nil {
t.Fatalf("Unmarshal marshaled map failed: %v", err)
}

if !reflect.DeepEqual(originalMap, marshaledMap) {
t.Errorf("Round-trip mismatch")
wantJSON, _ := json.MarshalIndent(originalMap, "", " ")
gotJSON, _ := json.MarshalIndent(marshaledMap, "", " ")
t.Errorf("--- want ---\n%s\n--- got ---\n%s\n", string(wantJSON), string(gotJSON))
}
}

func TestExplorerConfig_Fields(t *testing.T) {
var cfg Config
if err := json.Unmarshal([]byte(explorerJSON), &cfg); err != nil {
t.Fatalf("unmarshal failed: %v", err)
}

if len(cfg.ExplorerConfig) != 1 {
t.Fatalf("expected 1 explorerConfig, got %d", len(cfg.ExplorerConfig))
}

item := cfg.ExplorerConfig[0]
if item.TabTitle != "test" {
t.Errorf("TabTitle = %q, want %q", item.TabTitle, "test")
}

if len(item.Buttons) != 1 {
t.Fatalf("expected 1 button, got %d", len(item.Buttons))
}

btn := item.Buttons[0]
if btn.Type != "manifest" || btn.Title != "Download Manifest" {
t.Errorf("button mismatch: %+v", btn)
}

if btn.ActionArgs.ResourceIndexType != "file" {
t.Errorf("ActionArgs.ResourceIndexType = %q, want %q", btn.ActionArgs.ResourceIndexType, "file")
}

if val, ok := item.PreFilters["project_id"]; !ok {
t.Error("missing preFilters project_id")
} else {
// val will be []any since item.PreFilters is map[string]any
slice, ok := val.([]any)
if !ok || len(slice) != 1 || slice[0] != "proj1" {
t.Errorf("preFilters project_id = %v, want [proj1]", val)
}
}

if got := cfg.FileActions.Extensions["tiff"]; !reflect.DeepEqual(got, []string{"file_download", "file_image"}) {
t.Errorf("FileActions.Extensions[tiff] = %v, want %v", got, []string{"file_download", "file_image"})
}

if got := cfg.FileActions.Actions["file_image"]; got != "/image-viewer/view" {
t.Errorf("FileActions.Actions[file_image] = %q, want %q", got, "/image-viewer/view")
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading