From dc2ec024a90a260d9ab488bde4a5b1f2a15c2798 Mon Sep 17 00:00:00 2001 From: Valerie Walch Date: Fri, 13 Sep 2024 19:55:05 -0400 Subject: [PATCH 1/4] Added docker files. --- Dockerfile | 35 +++++++++++++++++++++++++++++++++++ compose.yaml | 5 +++++ config.yaml | 6 +++--- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 Dockerfile create mode 100644 compose.yaml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3561b5e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# syntax=docker/dockerfile:1 + +FROM golang:1.22.2 + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY auth ./auth +COPY config ./config +COPY db ./db +COPY docs ./docs +COPY handlers ./handlers +COPY models ./models +COPY services ./services +COPY setup ./setup +COPY utils ./utils +COPY webhooks ./webhooks +COPY data ./data +COPY web-ui ./web-ui +COPY config.yaml key.pem cert.pem main.go rsakey.pem build.sh run.sh ./ + +RUN CGO_ENABLED=1 GOOS=linux go build -o dingus-server + +# Optional: +# To bind to a TCP port, runtime parameters must be supplied to the docker command. +# But we can document in the Dockerfile what ports +# the application is going to listen on by default. +# https://docs.docker.com/reference/dockerfile/#expose +EXPOSE 443 + +# Run +CMD ["/app/run.sh"] \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..efdce01 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,5 @@ +services: + dingus-server: + build: . + ports: + - "443:443" \ No newline at end of file diff --git a/config.yaml b/config.yaml index 82df99a..cf2874c 100644 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,7 @@ -cert_file: .ssh/id_rsa/dev_pubkey.pem +cert_file: cert.pem contact_filter_query: "(Status eq Active or Status eq PendingRenewal) and 'Door Key' ne NULL" -database_path: db/data/tagsdb.sqlite -key_file: .ssh/id_rsa/dev_secret.pem +database_path: data/tagsdb.sqlite +key_file: key.pem tag_id_field_name: Door Key training_field_name: Safety Training wild_apricot_account_id: 232582 From ce2445c6fea1f3a89ce683822149ea1272de7f24 Mon Sep 17 00:00:00 2001 From: Valerie Walch Date: Sat, 28 Dec 2024 10:40:34 -0500 Subject: [PATCH 2/4] Added instructions for docker. --- README.md | 17 +++++++++++++++-- build.sh | 12 ++++++++++++ db/data/.gitignore | 5 ----- run.sh | 12 ++++++++++++ 4 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 build.sh delete mode 100644 db/data/.gitignore create mode 100644 run.sh diff --git a/README.md b/README.md index 556ba2c..13df6d8 100644 --- a/README.md +++ b/README.md @@ -48,15 +48,28 @@ This project is an RFID access control system's backend server written in Golang - `web-ui`: Frontend assets. ## Getting Started +### Docker +#### Build +`docker-compose build` +#### Run Interactive +`docker-compose up` -### Prerequisites +`ctrl-c` to stop. + +#### Run in background (Detached) +`docker-compose up -d` +#### Stop +`docker-compose down` + +### Native +#### Prerequisites - [Go](https://go.dev/doc/install) (latest stable version) - Access to [Wild Apricot API](https://gethelp.wildapricot.com/en/articles/182-using-wildapricot-s-api) - SSL certificate and key - GCC for SQLite Go package compilation (requires cgo) -### Setting CGO_ENABLED +#### Setting CGO_ENABLED To successfully build and run this project, `CGO_ENABLED` must be set to `1`. This allows for the compilation of C code, a requirement for the SQLite package used in the project. diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..dc70b99 --- /dev/null +++ b/build.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +export WILD_APRICOT_API_KEY=ilyglbbwzai5suuxt4nai0kqx5evvh +export WILD_APRICOT_WEBHOOK_TOKEN=08a7gv08abwDYGd77cxv980asdfy98zxc87 +export WILD_APRICOT_SSO_CLIENT_ID=t8jw60pj3f +export WILD_APRICOT_SSO_CLIENT_SECRET=5k8nct39ootl8wt0gkpxe22v8phi5a +export WILD_APRICOT_SSO_REDIRECT_URI=/replaceme +export LOG_LEVEL=INFO +export COOKIE_STORE_SECRET=cookiestoresecret +export CGO_ENABLED=1 +export GOOS=linux +/usr/local/go/bin/go build -o dingus-server \ No newline at end of file diff --git a/db/data/.gitignore b/db/data/.gitignore deleted file mode 100644 index c1462eb..0000000 --- a/db/data/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# This file is here in order to commit the empty data folder to the git repo. -# Ignore everything in this directory -* -# Except this file -!.gitignore \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..10f461b --- /dev/null +++ b/run.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +export WILD_APRICOT_API_KEY=ilyglbbwzai5suuxt4nai0kqx5evvh +export WILD_APRICOT_WEBHOOK_TOKEN=08a7gv08abwDYGd77cxv980asdfy98zxc87 +export WILD_APRICOT_SSO_CLIENT_ID=t8jw60pj3f +export WILD_APRICOT_SSO_CLIENT_SECRET=5k8nct39ootl8wt0gkpxe22v8phi5a +export WILD_APRICOT_SSO_REDIRECT_URI=/replaceme +export LOG_LEVEL=INFO +export COOKIE_STORE_SECRET=cookiestoresecret +export CGO_ENABLED=1 +export GOOS=linux +./dingus-server \ No newline at end of file From 098741e80c9bea0b0093050be9b7f75b2a7a850e Mon Sep 17 00:00:00 2001 From: Valerie Walch Date: Sat, 28 Dec 2024 14:23:46 -0500 Subject: [PATCH 3/4] ExtractTrainingLabel errors no longer stop processing. --- models/wildApricotContact.go | 4 ++-- services/dbService.go | 4 +++- services/wildApricotService.go | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/models/wildApricotContact.go b/models/wildApricotContact.go index ab11857..da26d9c 100644 --- a/models/wildApricotContact.go +++ b/models/wildApricotContact.go @@ -65,10 +65,10 @@ func (c *Contact) ExtractContactData(cfg *config.Config) (int, uint32, []string, trainingLabels, err := c.ExtractTrainingLabels(cfg) if err != nil { - return 0, 0, nil, fmt.Errorf("error extracting training labels for contact %d: %v", c.Id, err) + err = fmt.Errorf("error extracting training labels for contact %d: %v", c.Id, err) } - return c.Id, tagID, trainingLabels, nil + return c.Id, tagID, trainingLabels, err } func parseTagId(fieldValue FieldValue) (uint32, error) { diff --git a/services/dbService.go b/services/dbService.go index 6842d07..579b4b9 100644 --- a/services/dbService.go +++ b/services/dbService.go @@ -134,7 +134,8 @@ func (s *DBService) ProcessContactsData(contacts []models.Contact) error { for _, contact := range contacts { contactId, tagId, trainingLabels, err := contact.ExtractContactData(s.cfg) if err != nil { - return err + // output the error and save any data that was retrieved. + s.log.Error(err) } if contactId != 0 && tagId != 0 { @@ -167,6 +168,7 @@ func (s *DBService) ProcessContactsData(contacts []models.Contact) error { tx.Rollback() return err } + return tx.Commit() } diff --git a/services/wildApricotService.go b/services/wildApricotService.go index 4f23fe9..fa303d7 100644 --- a/services/wildApricotService.go +++ b/services/wildApricotService.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "net/url" "rfid-backend/config" @@ -47,7 +46,7 @@ func NewWildApricotService(cfg *config.Config, logger *logrus.Logger) *WildApric func readResponseBody(resp *http.Response) ([]byte, error) { defer resp.Body.Close() - return ioutil.ReadAll(resp.Body) + return io.ReadAll(resp.Body) } func handleHTTPError(resp *http.Response) error { @@ -169,6 +168,7 @@ func (s *WildApricotService) GetContacts() ([]models.Contact, error) { } s.log.Infof("Parsed %d contacts from response", len(contacts)) + return contacts, nil } From f8bc6c2b4aa99c6bde050a6b82a7c466d6a49807 Mon Sep 17 00:00:00 2001 From: Valerie Walch Date: Sun, 19 Oct 2025 14:51:02 -0400 Subject: [PATCH 4/4] Added paging to contact get. --- Dockerfile | 70 +++--- build.sh | 8 +- compose.yaml | 8 +- config/config.go | 1 + run.sh | 9 +- services/dbService_test.go | 428 ++++++++++++++++----------------- services/wildApricotService.go | 52 ++-- setup/setupRoutes.go | 2 +- 8 files changed, 297 insertions(+), 281 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3561b5e..4b43fb1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,35 +1,35 @@ -# syntax=docker/dockerfile:1 - -FROM golang:1.22.2 - -WORKDIR /app - -COPY go.mod go.sum ./ - -RUN go mod download - -COPY auth ./auth -COPY config ./config -COPY db ./db -COPY docs ./docs -COPY handlers ./handlers -COPY models ./models -COPY services ./services -COPY setup ./setup -COPY utils ./utils -COPY webhooks ./webhooks -COPY data ./data -COPY web-ui ./web-ui -COPY config.yaml key.pem cert.pem main.go rsakey.pem build.sh run.sh ./ - -RUN CGO_ENABLED=1 GOOS=linux go build -o dingus-server - -# Optional: -# To bind to a TCP port, runtime parameters must be supplied to the docker command. -# But we can document in the Dockerfile what ports -# the application is going to listen on by default. -# https://docs.docker.com/reference/dockerfile/#expose -EXPOSE 443 - -# Run -CMD ["/app/run.sh"] \ No newline at end of file +# syntax=docker/dockerfile:1 + +FROM golang:1.22.2 + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY auth ./auth +COPY config ./config +COPY db ./db +COPY docs ./docs +COPY handlers ./handlers +COPY models ./models +COPY services ./services +COPY setup ./setup +COPY utils ./utils +COPY webhooks ./webhooks +COPY data ./data +COPY web-ui ./web-ui +COPY config.yaml key.pem cert.pem main.go rsakey.pem build.sh run.sh ./ + +RUN CGO_ENABLED=1 GOOS=linux go build -o dingus-server + +# Optional: +# To bind to a TCP port, runtime parameters must be supplied to the docker command. +# But we can document in the Dockerfile what ports +# the application is going to listen on by default. +# https://docs.docker.com/reference/dockerfile/#expose +EXPOSE 443 + +# Run +CMD ["sh","/app/run.sh"] \ No newline at end of file diff --git a/build.sh b/build.sh index dc70b99..93f6d93 100644 --- a/build.sh +++ b/build.sh @@ -1,10 +1,10 @@ #!/bin/sh -export WILD_APRICOT_API_KEY=ilyglbbwzai5suuxt4nai0kqx5evvh +export WILD_APRICOT_API_KEY=v97hk30e4t8qm1nrwb6exsfibiisvy export WILD_APRICOT_WEBHOOK_TOKEN=08a7gv08abwDYGd77cxv980asdfy98zxc87 -export WILD_APRICOT_SSO_CLIENT_ID=t8jw60pj3f -export WILD_APRICOT_SSO_CLIENT_SECRET=5k8nct39ootl8wt0gkpxe22v8phi5a -export WILD_APRICOT_SSO_REDIRECT_URI=/replaceme +export WILD_APRICOT_SSO_CLIENT_ID=654628fgDFc68hH +export WILD_APRICOT_SSO_CLIENT_SECRET=07pb835b7pw3s8r0qrvmtlipuwjtt6 +export WILD_APRICOT_SSO_REDIRECT_URI=https://dingus.hackpgh.org/web-ui/home export LOG_LEVEL=INFO export COOKIE_STORE_SECRET=cookiestoresecret export CGO_ENABLED=1 diff --git a/compose.yaml b/compose.yaml index efdce01..dd75259 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,5 +1,5 @@ -services: - dingus-server: - build: . - ports: +services: + dingus-server: + build: . + ports: - "443:443" \ No newline at end of file diff --git a/config/config.go b/config/config.go index ffd4265..6622116 100644 --- a/config/config.go +++ b/config/config.go @@ -31,6 +31,7 @@ type Config struct { CookieStoreSecret string `mapstructure:"cookie_store_secret" json:"cookie_store_secret"` WildApricotApiKey string WildApricotWebhookToken string + RFIDFieldName string log *logrus.Logger } diff --git a/run.sh b/run.sh index 10f461b..d30ff8f 100644 --- a/run.sh +++ b/run.sh @@ -1,10 +1,11 @@ #!/bin/sh -export WILD_APRICOT_API_KEY=ilyglbbwzai5suuxt4nai0kqx5evvh +export WILD_APRICOT_API_KEY=v97hk30e4t8qm1nrwb6exsfibiisvy export WILD_APRICOT_WEBHOOK_TOKEN=08a7gv08abwDYGd77cxv980asdfy98zxc87 -export WILD_APRICOT_SSO_CLIENT_ID=t8jw60pj3f -export WILD_APRICOT_SSO_CLIENT_SECRET=5k8nct39ootl8wt0gkpxe22v8phi5a -export WILD_APRICOT_SSO_REDIRECT_URI=/replaceme +export WILD_APRICOT_SSO_CLIENT_ID=654628fgDFc68hH +export WILD_APRICOT_SSO_CLIENT_SECRET=07pb835b7pw3s8r0qrvmtlipuwjtt6 +export WILD_APRICOT_SSO_REDIRECT_URI=https://dingus.hackpgh.org/web-ui/home +#export WILD_APRICOT_SSO_REDIRECT_URI=https://localhost/web-ui/home export LOG_LEVEL=INFO export COOKIE_STORE_SECRET=cookiestoresecret export CGO_ENABLED=1 diff --git a/services/dbService_test.go b/services/dbService_test.go index 977757a..5cb480c 100644 --- a/services/dbService_test.go +++ b/services/dbService_test.go @@ -1,214 +1,214 @@ -package services - -import ( - "database/sql" - "rfid-backend/config" - "testing" - - _ "github.com/mattn/go-sqlite3" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func mockConfig() *config.Config { - return &config.Config{ - CertFile: "path/to/test/cert.pem", - KeyFile: "path/to/test/key.pem", - DatabasePath: "path/to/test/database.db", - RFIDFieldName: "RFID", - TrainingFieldName: "Training", - WildApricotAccountId: 12345, - ContactFilterQuery: "status eq Active or status eq 'Pending - Renewal'", - } -} - -func setupTestDB(t *testing.T) *sql.DB { - // Create an in-memory SQLite database - db, err := sql.Open("sqlite3", ":memory:") - require.NoError(t, err) - - // Create Members table - _, err = db.Exec(` - CREATE TABLE IF NOT EXISTS members ( - tag_id INTEGER PRIMARY KEY, - membership_level INTEGER NOT NULL - );`) - require.NoError(t, err) - - // Create SafetyTrainings table - _, err = db.Exec(` - CREATE TABLE IF NOT EXISTS trainings ( - label TEXT PRIMARY KEY - );`) - require.NoError(t, err) - - // Create SafetyTrainingMembersLink table - _, err = db.Exec(` - CREATE TABLE IF NOT EXISTS members_trainings_link ( - tag_id INTEGER NOT NULL, - label TEXT NOT NULL, - FOREIGN KEY (tag_id) REFERENCES members(tag_id), - FOREIGN KEY (label) REFERENCES trainings(label), - UNIQUE (tag_id, label) - );`) - require.NoError(t, err) - - return db -} - -func TestGetAllRFIDs(t *testing.T) { - cfg := mockConfig() - - db := setupTestDB(t) - defer db.Close() - - // Insert test data into members table - _, err := db.Exec("INSERT INTO members (tag_id, membership_level) VALUES (11111, 1), (22222, 1)") - require.NoError(t, err) - - dbService := NewDBService(db, cfg) - - // Execute the test function - rfids, err := dbService.GetAllRFIDs() - - // Assertions - assert.NoError(t, err) - assert.Len(t, rfids, 2) - assert.Equal(t, uint32(11111), rfids[0]) - assert.Equal(t, uint32(22222), rfids[1]) -} - -func TestGetRFIDsForMachine(t *testing.T) { - cfg := mockConfig() - - db := setupTestDB(t) - defer db.Close() - - // Insert test data into members table - _, err := db.Exec("INSERT INTO members (tag_id, membership_level) VALUES (12345, 1), (67890, 1)") - require.NoError(t, err) - - // Insert test data into members_trainings_link table - _, err = db.Exec("INSERT INTO members_trainings_link (tag_id, label) VALUES (12345, 'MachineA'), (67890, 'MachineA')") - require.NoError(t, err) - - dbService := NewDBService(db, cfg) - - tags, err := dbService.GetRFIDsForMachine("MachineA") - assert.NoError(t, err) - assert.Len(t, tags, 2) - assert.Equal(t, uint32(12345), tags[0]) - assert.Equal(t, uint32(67890), tags[1]) -} - -func TestInsertOrUpdateMembers(t *testing.T) { - cfg := mockConfig() - - db := setupTestDB(t) - - // Start a transaction - tx, err := db.Begin() - require.NoError(t, err) - - dbService := NewDBService(db, cfg) - - allRFIDs := []uint32{1234, 5678} - err = dbService.insertOrUpdateMembers(tx, allRFIDs) - assert.NoError(t, err) - - // Commit the transaction - err = tx.Commit() - assert.NoError(t, err) - // Verify the data - var count int - err = db.QueryRow("SELECT COUNT(*) FROM members").Scan(&count) - assert.NoError(t, err) - assert.Equal(t, 2, count) -} - -func TestInsertTrainings(t *testing.T) { - cfg := mockConfig() - - db := setupTestDB(t) - - tx, err := db.Begin() - require.NoError(t, err) - - dbService := NewDBService(db, cfg) - - trainingMap := map[string][]uint32{ - "Metal Lathe": {1234}, - "CNC": {5678}, - } - err = dbService.insertTrainings(tx, trainingMap) - assert.NoError(t, err) - - err = tx.Commit() - assert.NoError(t, err) - - // Verify the data - var count int - err = db.QueryRow("SELECT COUNT(*) FROM trainings").Scan(&count) - assert.NoError(t, err) - assert.Equal(t, len(trainingMap), count) -} - -func TestManageMemberTrainingLinks(t *testing.T) { - cfg := mockConfig() - - db := setupTestDB(t) - - // Start a transaction - tx, err := db.Begin() - require.NoError(t, err) - - dbService := NewDBService(db, cfg) - - trainingMap := map[string][]uint32{ - "Metal Lathe": {1234}, - "CNC": {5678}, - } - err = dbService.manageMemberTrainingLinks(tx, trainingMap) - assert.NoError(t, err) - - err = tx.Commit() - assert.NoError(t, err) - - // Verify the data - var count int - err = db.QueryRow("SELECT COUNT(*) FROM members_trainings_link").Scan(&count) - assert.NoError(t, err) - assert.Equal(t, 2, count) // Assuming each training has one RFID -} - -func TestDeleteInactiveMembers(t *testing.T) { - cfg := mockConfig() - - db := setupTestDB(t) - defer db.Close() - - // Insert 2 test members into members table - _, err := db.Exec("INSERT INTO members (tag_id, membership_level) VALUES (1234, 1), (67890, 1)") - require.NoError(t, err) - - // Start a transaction - tx, err := db.Begin() - require.NoError(t, err) - - dbService := NewDBService(db, cfg) - - allRFIDs := []uint32{1234} // only 1 active member, remove the inactive record - err = dbService.deleteInactiveMembers(tx, allRFIDs) - assert.NoError(t, err) - - // Commit the transaction - err = tx.Commit() - assert.NoError(t, err) - - // Verify that only the active members remain - var count int - err = db.QueryRow("SELECT COUNT(*) FROM members").Scan(&count) - assert.NoError(t, err) - assert.Equal(t, 1, count) // Expect only 1 active member in the table -} +package services + +import ( + "database/sql" + "rfid-backend/config" + "testing" + + _ "github.com/mattn/go-sqlite3" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockConfig() *config.Config { + return &config.Config{ + CertFile: "path/to/test/cert.pem", + KeyFile: "path/to/test/key.pem", + DatabasePath: "path/to/test/database.db", + RFIDFieldName: "RFID", + TrainingFieldName: "Training", + WildApricotAccountId: 12345, + ContactFilterQuery: "status eq Active or status eq 'Pending - Renewal'", + } +} + +func setupTestDB(t *testing.T) *sql.DB { + // Create an in-memory SQLite database + db, err := sql.Open("sqlite3", ":memory:") + require.NoError(t, err) + + // Create Members table + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS members ( + tag_id INTEGER PRIMARY KEY, + membership_level INTEGER NOT NULL + );`) + require.NoError(t, err) + + // Create SafetyTrainings table + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS trainings ( + label TEXT PRIMARY KEY + );`) + require.NoError(t, err) + + // Create SafetyTrainingMembersLink table + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS members_trainings_link ( + tag_id INTEGER NOT NULL, + label TEXT NOT NULL, + FOREIGN KEY (tag_id) REFERENCES members(tag_id), + FOREIGN KEY (label) REFERENCES trainings(label), + UNIQUE (tag_id, label) + );`) + require.NoError(t, err) + + return db +} + +func TestGetAllRFIDs(t *testing.T) { + cfg := mockConfig() + + db := setupTestDB(t) + defer db.Close() + + // Insert test data into members table + _, err := db.Exec("INSERT INTO members (tag_id, membership_level) VALUES (11111, 1), (22222, 1)") + require.NoError(t, err) + + dbService := NewDBService(db, cfg) + + // Execute the test function + rfids, err := dbService.GetAllRFIDs() + + // Assertions + assert.NoError(t, err) + assert.Len(t, rfids, 2) + assert.Equal(t, uint32(11111), rfids[0]) + assert.Equal(t, uint32(22222), rfids[1]) +} + +func TestGetRFIDsForMachine(t *testing.T) { + cfg := mockConfig() + + db := setupTestDB(t) + defer db.Close() + + // Insert test data into members table + _, err := db.Exec("INSERT INTO members (tag_id, membership_level) VALUES (12345, 1), (67890, 1)") + require.NoError(t, err) + + // Insert test data into members_trainings_link table + _, err = db.Exec("INSERT INTO members_trainings_link (tag_id, label) VALUES (12345, 'MachineA'), (67890, 'MachineA')") + require.NoError(t, err) + + dbService := NewDBService(db, cfg) + + tags, err := dbService.GetRFIDsForMachine("MachineA") + assert.NoError(t, err) + assert.Len(t, tags, 2) + assert.Equal(t, uint32(12345), tags[0]) + assert.Equal(t, uint32(67890), tags[1]) +} + +func TestInsertOrUpdateMembers(t *testing.T) { + cfg := mockConfig() + + db := setupTestDB(t) + + // Start a transaction + tx, err := db.Begin() + require.NoError(t, err) + + dbService := NewDBService(db, cfg) + + allRFIDs := []uint32{1234, 5678} + err = dbService.insertOrUpdateMembers(tx, allRFIDs) + assert.NoError(t, err) + + // Commit the transaction + err = tx.Commit() + assert.NoError(t, err) + // Verify the data + var count int + err = db.QueryRow("SELECT COUNT(*) FROM members").Scan(&count) + assert.NoError(t, err) + assert.Equal(t, 2, count) +} + +func TestInsertTrainings(t *testing.T) { + cfg := mockConfig() + + db := setupTestDB(t) + + tx, err := db.Begin() + require.NoError(t, err) + + dbService := NewDBService(db, cfg) + + trainingMap := map[string][]uint32{ + "Metal Lathe": {1234}, + "CNC": {5678}, + } + err = dbService.insertTrainings(tx, trainingMap) + assert.NoError(t, err) + + err = tx.Commit() + assert.NoError(t, err) + + // Verify the data + var count int + err = db.QueryRow("SELECT COUNT(*) FROM trainings").Scan(&count) + assert.NoError(t, err) + assert.Equal(t, len(trainingMap), count) +} + +func TestManageMemberTrainingLinks(t *testing.T) { + cfg := mockConfig() + + db := setupTestDB(t) + + // Start a transaction + tx, err := db.Begin() + require.NoError(t, err) + + dbService := NewDBService(db, cfg) + + trainingMap := map[string][]uint32{ + "Metal Lathe": {1234}, + "CNC": {5678}, + } + err = dbService.manageMemberTrainingLinks(tx, trainingMap) + assert.NoError(t, err) + + err = tx.Commit() + assert.NoError(t, err) + + // Verify the data + var count int + err = db.QueryRow("SELECT COUNT(*) FROM members_trainings_link").Scan(&count) + assert.NoError(t, err) + assert.Equal(t, 2, count) // Assuming each training has one RFID +} + +func TestDeleteInactiveMembers(t *testing.T) { + cfg := mockConfig() + + db := setupTestDB(t) + defer db.Close() + + // Insert 2 test members into members table + _, err := db.Exec("INSERT INTO members (tag_id, membership_level) VALUES (1234, 1), (67890, 1)") + require.NoError(t, err) + + // Start a transaction + tx, err := db.Begin() + require.NoError(t, err) + + dbService := NewDBService(db, cfg) + + allRFIDs := []uint32{1234} // only 1 active member, remove the inactive record + err = dbService.deleteInactiveMembers(tx, allRFIDs) + assert.NoError(t, err) + + // Commit the transaction + err = tx.Commit() + assert.NoError(t, err) + + // Verify that only the active members remain + var count int + err = db.QueryRow("SELECT COUNT(*) FROM members").Scan(&count) + assert.NoError(t, err) + assert.Equal(t, 1, count) // Expect only 1 active member in the table +} diff --git a/services/wildApricotService.go b/services/wildApricotService.go index fa303d7..2cd6239 100644 --- a/services/wildApricotService.go +++ b/services/wildApricotService.go @@ -27,6 +27,7 @@ type WildApricotService struct { } var wildApricotSvc = utils.NewSingleton(&WildApricotService{}) +var pageSize = 100 func NewWildApricotService(cfg *config.Config, logger *logrus.Logger) *WildApricotService { return wildApricotSvc.Get(func() interface{} { @@ -147,29 +148,42 @@ func (s *WildApricotService) makeHTTPRequest(method, url string, body io.Reader) } func (s *WildApricotService) GetContacts() ([]models.Contact, error) { - contactURL := s.buildURL("/%d/Contacts?$async=false&$filter=%s", - s.cfg.WildApricotAccountId, - url.QueryEscape(s.cfg.ContactFilterQuery)) - - resp, err := s.makeHTTPRequest("GET", contactURL, nil) - if err != nil { - s.logError("creating request for contacts", err) - return nil, err - } + skip := 0 + allContacts := []models.Contact{} + for { + contactURL := s.buildURL("/%d/Contacts?$async=false&$filter=%s&$top=%d&$skip=%d", + s.cfg.WildApricotAccountId, + url.QueryEscape(s.cfg.ContactFilterQuery), + pageSize, + skip) + + // Increment skip by page size. + skip += pageSize + + resp, err := s.makeHTTPRequest("GET", contactURL, nil) + if err != nil { + s.logError("creating request for contacts", err) + return nil, err + } - if err := handleHTTPError(resp); err != nil { - s.logError("handling HTTP error for contacts", err) - return nil, err - } + if err := handleHTTPError(resp); err != nil { + s.logError("handling HTTP error for contacts", err) + return nil, err + } - contacts, err := s.parseHTTPResponse(resp) - if err != nil { - s.logError("parsing HTTP response", err) - } + contacts, err := s.parseHTTPResponse(resp) + if err != nil { + s.logError("parsing HTTP response", err) + } - s.log.Infof("Parsed %d contacts from response", len(contacts)) + allContacts = append(allContacts, contacts...) - return contacts, nil + if len(contacts) < pageSize { + break + } + } + s.log.Infof("All Contacts Length %d", len(allContacts)) + return allContacts, nil } func (s *WildApricotService) GetContact(contactId int) (*models.Contact, error) { diff --git a/setup/setupRoutes.go b/setup/setupRoutes.go index dcb7b7a..4386624 100644 --- a/setup/setupRoutes.go +++ b/setup/setupRoutes.go @@ -31,7 +31,7 @@ func SetupRoutes(router *gin.Engine, cfg *config.Config, db *sql.DB, logger *log RedirectURL: cfg.SSORedirectURI, Scopes: []string{"contacts_me"}, Endpoint: oauth2.Endpoint{ - AuthURL: "https://davidbouw36728.wildapricot.org/sys/login/OAuthLogin", + AuthURL: "https://members.hackpgh.org/sys/login/OAuthLogin", TokenURL: "https://oauth.wildapricot.org/auth/token", }, }