Updates Containerize
This commit is contained in:
23
.github/workflows/ci.yaml
vendored
Normal file
23
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: CI
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run linter
|
||||
run: make lint
|
||||
- name: Run unit tests
|
||||
run: make unit-test
|
||||
- name: Get unit test coverage
|
||||
run: make unit-test-coverage
|
||||
- name: Build Linux binary
|
||||
run: make PLATFORM=linux/amd64
|
||||
- name: Build Windows binary
|
||||
run: make PLATFORM=windows/amd64
|
||||
71
Dockerfile
Normal file
71
Dockerfile
Normal file
@@ -0,0 +1,71 @@
|
||||
# Containerized-go dockerfile
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} golang:1.24.3-alpine AS base
|
||||
WORKDIR /src
|
||||
ENV CGO_ENABLED=1
|
||||
RUN apk update
|
||||
RUN apk add gcc g++
|
||||
RUN mkdir /out
|
||||
|
||||
FROM base AS buildbase
|
||||
COPY go.* .
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go mod download
|
||||
|
||||
FROM buildbase AS build
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/example .
|
||||
|
||||
FROM base AS unit-test
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
go test -v -coverprofile=/out/cover.out ./...
|
||||
|
||||
FROM golangci/golangci-lint:v2.1.6-alpine AS lint-base
|
||||
|
||||
FROM base AS lint
|
||||
RUN --mount=target=. \
|
||||
--mount=from=lint-base,src=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/root/.cache/golangci-lint \
|
||||
golangci-lint run --timeout 10m0s ./...
|
||||
|
||||
FROM base AS mod-init-base
|
||||
WORKDIR /out
|
||||
COPY . .
|
||||
ENV MODNAME tastytrade
|
||||
RUN go mod init "${MODNAME}" && go mod tidy
|
||||
|
||||
FROM base AS mod-tidy
|
||||
WORKDIR /out
|
||||
COPY . .
|
||||
RUN go mod tidy
|
||||
|
||||
FROM scratch AS mod-init
|
||||
COPY --from=mod-init-base /out/go.mod /go.mod
|
||||
COPY --from=mod-init-base /out/go.sum /go.sum
|
||||
|
||||
FROM scratch AS tidy
|
||||
COPY --from=mod-tidy /out/go.mod /go.mod
|
||||
COPY --from=mod-tidy /out/go.sum /go.sum
|
||||
COPY --from=mod-tidy /out/go.mod /go.mod
|
||||
|
||||
FROM scratch AS unit-test-coverage
|
||||
COPY --from=unit-test /out/cover.out /cover.out
|
||||
|
||||
FROM scratch AS bin-unix
|
||||
COPY --from=build /out/example /
|
||||
|
||||
FROM bin-unix AS bin-linux
|
||||
FROM bin-unix AS bin-darwin
|
||||
|
||||
FROM scratch AS bin-windows
|
||||
COPY --from=build /out/example /example.exe
|
||||
|
||||
FROM bin-${TARGETOS} AS bin
|
||||
37
Makefile
Normal file
37
Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
all: bin/example
|
||||
test: lint unit-test
|
||||
|
||||
PLATFORM=local
|
||||
UTIL_TAG=1.24.3-alpine
|
||||
|
||||
export DOCKER_BUILDKIT=1
|
||||
|
||||
.PHONY: bin/example
|
||||
bin/example:
|
||||
@docker build . --target bin \
|
||||
--output bin/ \
|
||||
--platform ${PLATFORM}
|
||||
|
||||
.PHONY: unit-test
|
||||
unit-test:
|
||||
@docker build . --target unit-test
|
||||
|
||||
.PHONY: unit-test-coverage
|
||||
unit-test-coverage:
|
||||
@docker build . --target unit-test-coverage \
|
||||
--output coverage/
|
||||
cat coverage/cover.out
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@docker build . --target lint
|
||||
|
||||
.PHONY: tidy
|
||||
tidy:
|
||||
export DOCKER_BUILDKIT=1
|
||||
@docker build . --target tidy --output .
|
||||
|
||||
.PHONY: init
|
||||
init:
|
||||
export DOCKER_BUILDKIT=1
|
||||
@docker build . --target mod-init --output .
|
||||
@@ -1,6 +1,6 @@
|
||||
# Go Tastytrade Open API Wrapper
|
||||
|
||||

|
||||

|
||||
|
||||
Go API wrapper for the Tastytrade Open API
|
||||
|
||||
|
||||
7
api.go
7
api.go
@@ -14,7 +14,9 @@ const (
|
||||
// TastytradeAPI represents the Tastytrade API client
|
||||
type TastytradeAPI struct {
|
||||
httpClient *http.Client
|
||||
user string
|
||||
authToken string
|
||||
authExpire time.Time
|
||||
remToken string
|
||||
host string
|
||||
}
|
||||
@@ -31,6 +33,11 @@ func NewTastytradeAPI(hosts ...string) *TastytradeAPI {
|
||||
}
|
||||
}
|
||||
|
||||
// get logged in User
|
||||
func (api *TastytradeAPI) User() string {
|
||||
return api.user
|
||||
}
|
||||
|
||||
// fetchData sends a GET request to the specified URL with authorization
|
||||
func (api *TastytradeAPI) fetchData(url string) (map[string]interface{}, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
|
||||
6
go.mod
6
go.mod
@@ -1,3 +1,5 @@
|
||||
module sancus.carpanet.net/sjc/tastytrade
|
||||
module tastytrade
|
||||
|
||||
go 1.23
|
||||
go 1.24.3
|
||||
|
||||
require sancus.carpanet.net/sjc/tastytrade v0.0.0-20250507051921-46dbff053071
|
||||
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
sancus.carpanet.net/sjc/tastytrade v0.0.0-20250507051921-46dbff053071 h1:tkqr+I0vRU0f1VHW/rwZQ6Iv9viIgo1ThpLXxo/CoBI=
|
||||
sancus.carpanet.net/sjc/tastytrade v0.0.0-20250507051921-46dbff053071/go.mod h1:w85F/rGX4Lt1UrvVtRE+muoc4wI4A0GSMqXIj2mN8FI=
|
||||
58
sessions.go
58
sessions.go
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
@@ -15,8 +16,9 @@ type User struct {
|
||||
}
|
||||
|
||||
type AuthData struct {
|
||||
User User `json:"user"`
|
||||
SessionToken string `json:"session-token"`
|
||||
User User `json:"user"`
|
||||
SessionToken string `json:"session-token"`
|
||||
SessionExpire time.Time `json:"session-expiration"`
|
||||
RememberToken string `json:"remember-token"`
|
||||
}
|
||||
|
||||
@@ -59,9 +61,10 @@ func (api *TastytradeAPI) Authenticate2(username, password string, remember bool
|
||||
return err
|
||||
}
|
||||
|
||||
api.authToken = authResponse.Data.SessionToken
|
||||
if remember {
|
||||
api.remToken = authResponse.Data.RememberToken
|
||||
api.authToken = authResponse.Data.SessionToken
|
||||
api.authExpire = authResponse.Data.SessionExpire
|
||||
if (remember) {
|
||||
api.remToken = authResponse.Data.RememberToken
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -69,3 +72,48 @@ func (api *TastytradeAPI) Authenticate2(username, password string, remember bool
|
||||
func (api *TastytradeAPI) Authenticate(username, password string) error {
|
||||
return api.Authenticate2(username, password, false)
|
||||
}
|
||||
|
||||
// Redeem a remember token to get a session
|
||||
|
||||
func (api *TastytradeAPI) AuthRemember(remember bool) error {
|
||||
var remStr string
|
||||
if remember {
|
||||
remStr = "true"
|
||||
} else {
|
||||
remStr = "false"
|
||||
}
|
||||
authURL := fmt.Sprintf("%s/sessions", api.host)
|
||||
authData := map[string]string{
|
||||
"login": api.user,
|
||||
"remember-token": api.remToken,
|
||||
"remember-me": remStr,
|
||||
}
|
||||
authBody, err := json.Marshal(authData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := api.httpClient.Post(authURL, "application/json", bytes.NewReader(authBody))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
return fmt.Errorf("authentication failed: status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
authResponse := AuthResponse{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&authResponse); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
api.authToken = authResponse.Data.SessionToken
|
||||
if (remember) {
|
||||
api.remToken = authResponse.Data.RememberToken
|
||||
} else {
|
||||
api.remToken = ""
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package tastytrade
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,7 @@ func TestAuthenticate(t *testing.T) {
|
||||
|
||||
func TestAuthenticate2(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Write([]byte(`{"context": "test", "data": {"user": {"email": "test@example.com", "username": "testuser", "external-id": "123", "is-confirmed": true}, "session-token": "testtoken", "remember-token": "remember"}}`))
|
||||
rw.Write([]byte(`{"context": "test", "data": {"user": {"email": "test@example.com", "username": "testuser", "external-id": "123", "is-confirmed": true}, "session-token": "testtoken", "remember-token": "remember", "session-expiration": "2024-09-12T20:25:32.440Z"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
@@ -48,4 +49,41 @@ func TestAuthenticate2(t *testing.T) {
|
||||
if (api.remToken != "remember") {
|
||||
t.Errorf("Expected remember token remember, got remember token %s", api.remToken)
|
||||
}
|
||||
|
||||
et, _ := time.Parse(time.RFC3339, "2024-09-12T20:25:32.440Z")
|
||||
if (api.authExpire != et) {
|
||||
t.Errorf("Expected expire %s, got %s", et, api.authExpire)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthRemember(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Write([]byte(`{"context": "test", "data": {"user": {"email": "test@example.com", "username": "testuser", "external-id": "123", "is-confirmed": true}, "session-token": "testtoken", "remember-token": "remember-new"}}`))
|
||||
}))
|
||||
var err error
|
||||
defer server.Close()
|
||||
api := NewTastytradeAPI(server.URL)
|
||||
err = api.Authenticate2("testuser", "testpassword", true)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil, got %v", err)
|
||||
}
|
||||
|
||||
api.remToken = "XXBADVALUEXX"
|
||||
err = api.AuthRemember(true)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil, got %v", err)
|
||||
}
|
||||
|
||||
if (api.remToken != "remember-new") {
|
||||
t.Errorf("Expected remember token remember, got remember token %s", api.remToken)
|
||||
}
|
||||
|
||||
err = api.AuthRemember(false)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil, got %v", err)
|
||||
}
|
||||
|
||||
if (len(api.remToken) > 0) {
|
||||
t.Errorf("Expected empty remember token, got remember token %s", api.remToken)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user