feat: CI/CD pipeline + Learning/Medicine/Travel services
Some checks failed
Build and Deploy GooSeek / build-backend (push) Failing after 1m4s
Build and Deploy GooSeek / build-webui (push) Failing after 1m2s
Build and Deploy GooSeek / deploy (push) Has been skipped

- Add Gitea Actions workflow for automated build & deploy
- Add K8s manifests: webui, travel-svc, medicine-svc, sandbox-svc
- Update kustomization for localhost:5000 registry
- Add ingress for gooseek.ru and api.gooseek.ru
- Learning cabinet with onboarding, courses, sandbox integration
- Medicine service with symptom analysis and doctor matching
- Travel service with itinerary planning
- Server setup scripts (NVIDIA/CUDA, K3s, Gitea runner)

Made-with: Cursor
This commit is contained in:
home
2026-03-02 20:25:44 +03:00
parent 08bd41e75c
commit ab48a0632b
92 changed files with 15562 additions and 2198 deletions

View File

@@ -1,6 +1,7 @@
package agent
import (
"bytes"
"context"
"encoding/json"
"fmt"
@@ -40,6 +41,24 @@ func NewTravelDataClient(baseURL string) *TravelDataClient {
}
func (c *TravelDataClient) doWithRetry(ctx context.Context, req *http.Request) (*http.Response, error) {
// http.Request.Clone does NOT recreate the Body. If we retry a request with a Body,
// we must be able to recreate it; otherwise retries will send an empty body and may
// fail with ContentLength/body length mismatch.
var bodyCopy []byte
if req.Body != nil && req.GetBody == nil {
b, err := io.ReadAll(req.Body)
if err != nil {
return nil, fmt.Errorf("read request body for retry: %w", err)
}
_ = req.Body.Close()
bodyCopy = b
req.GetBody = func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(bodyCopy)), nil
}
req.Body = io.NopCloser(bytes.NewReader(bodyCopy))
req.ContentLength = int64(len(bodyCopy))
}
var lastErr error
for attempt := 0; attempt <= c.maxRetries; attempt++ {
if attempt > 0 {
@@ -51,7 +70,18 @@ func (c *TravelDataClient) doWithRetry(ctx context.Context, req *http.Request) (
}
}
resp, err := c.httpClient.Do(req.Clone(ctx))
reqAttempt := req.Clone(ctx)
if req.GetBody != nil {
rc, err := req.GetBody()
if err != nil {
lastErr = err
continue
}
reqAttempt.Body = rc
reqAttempt.ContentLength = req.ContentLength
}
resp, err := c.httpClient.Do(reqAttempt)
if err != nil {
lastErr = err
continue
@@ -306,13 +336,16 @@ func (c *TravelDataClient) SearchHotels(ctx context.Context, lat, lng float64, c
// PlaceResult represents a place from 2GIS Places API.
type PlaceResult struct {
ID string `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
Type string `json:"type"`
Purpose string `json:"purpose"`
ID string `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
Type string `json:"type"`
Purpose string `json:"purpose"`
Rating float64 `json:"rating"`
ReviewCount int `json:"reviewCount"`
Schedule map[string]string `json:"schedule,omitempty"`
}
func (c *TravelDataClient) SearchPlaces(ctx context.Context, query string, lat, lng float64, radius int) ([]PlaceResult, error) {