From c5c292465f5ab98315dc275535ed0836d937bf96 Mon Sep 17 00:00:00 2001 From: Anderson Pham Date: Tue, 28 Oct 2025 20:14:30 -0700 Subject: [PATCH 01/13] init db with officer json file --- init_db.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 init_db.go diff --git a/init_db.go b/init_db.go new file mode 100644 index 0000000..0edcacf --- /dev/null +++ b/init_db.go @@ -0,0 +1,133 @@ +package main + +import ( + "database/sql" + "encoding/json" + "fmt" + "log" + "os" + "strings" + _ "github.com/mattn/go-sqlite3" +) + +type Officer struct { + FullName string `json:"fullName"` + Picture string `json:"picture"` + Positions map[string][]struct { + Title string `json:"title"` + Tier int `json:"tier"` + } `json:"positions"` + Discord string `json:"discord,omitempty"` +} + +func main() { + data, err := os.ReadFile("officers.json") + if err != nil { + log.Fatal("Error reading JSON file:", err) + } + + var officers []Officer + err = json.Unmarshal(data, &officers) + if err != nil { + log.Fatal("Error unmarshaling JSON:", err) + } + + db, err := sql.Open("sqlite3", "./dev.db") + if err != nil { + log.Fatal("Error opening database:", err) + } + defer db.Close() + + // Create tables if they don't exist + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS officer ( + uuid CHAR(4) PRIMARY KEY, + full_name VARCHAR(30) NOT NULL, + picture VARCHAR(37), + github VARCHAR(64), + discord VARCHAR(32) + ); + + CREATE TABLE IF NOT EXISTS tier ( + tier INT PRIMARY KEY, + title VARCHAR(40), + t_index INT, + team VARCHAR(20) + ); + + CREATE TABLE IF NOT EXISTS position ( + oid CHAR(4) NOT NULL, + semester CHAR(3) NOT NULL, + tier INT NOT NULL, + full_name VARCHAR(30) NOT NULL, + title VARCHAR(40), + team VARCHAR(20), + PRIMARY KEY (oid, semester, tier), + CONSTRAINT fk_officers FOREIGN KEY (oid) REFERENCES officer (uuid), + CONSTRAINT fk_tiers FOREIGN KEY (tier) REFERENCES tier(tier) + ); + `) + if err != nil { + log.Fatal("Error creating tables:", err) + } + + // Prepare statements + officerStmt, err := db.Prepare("INSERT OR IGNORE INTO officer (uuid, full_name, picture, discord) VALUES (?, ?, ?, ?)") + if err != nil { + log.Fatal("Error preparing officer statement:", err) + } + defer officerStmt.Close() + + tierStmt, err := db.Prepare("INSERT OR IGNORE INTO tier (tier, title) VALUES (?, ?)") + if err != nil { + log.Fatal("Error preparing tier statement:", err) + } + defer tierStmt.Close() + + positionStmt, err := db.Prepare("INSERT OR IGNORE INTO position (oid, semester, tier, full_name, title) VALUES (?, ?, ?, ?, ?)") + if err != nil { + log.Fatal("Error preparing position statement:", err) + } + defer positionStmt.Close() + + // Insert officers + for i, officer := range officers { + // Generate sequential 4-digit ID (0001, 0002, etc.) + sequentialID := fmt.Sprintf("%04d", i+1) + + // Insert officer + _, err = officerStmt.Exec(sequentialID, officer.FullName, officer.Picture, officer.Discord) + if err != nil { + log.Printf("Error inserting officer %s: %v", officer.FullName, err) + continue + } + + // Insert all positions the officer holds + for semester, positions := range officer.Positions { + for _, pos := range positions { + // Insert tier if it doesn't exist yet + _, err = tierStmt.Exec(pos.Tier, pos.Title) + if err != nil { + log.Printf("Error inserting tier %d for officer %s: %v", pos.Tier, officer.FullName, err) + continue + } + + // Insert position + _, err = positionStmt.Exec( + sequentialID, + strings.ToUpper(semester), + pos.Tier, + officer.FullName, + pos.Title, + ) + if err != nil { + log.Printf("Error inserting position for officer %s: %v", officer.FullName, err) + continue + } + } + } + } + + fmt.Println("Database population completed successfully!") + fmt.Printf("Processed %d officers\n", len(officers)) +} \ No newline at end of file From da828d1660fda0c0d26be3c6af38c840738c2be1 Mon Sep 17 00:00:00 2001 From: Anderson Pham Date: Tue, 28 Oct 2025 20:15:24 -0700 Subject: [PATCH 02/13] init db with officer json file --- officers.json | 1376 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1376 insertions(+) create mode 100644 officers.json diff --git a/officers.json b/officers.json new file mode 100644 index 0000000..eb604ba --- /dev/null +++ b/officers.json @@ -0,0 +1,1376 @@ +[ + { + "fullName": "Sama Ahmed", + "picture": "sama-ahmed.webp", + "positions": { + "S24": [{ "title": "Design Officer", "tier": 16 }], + "F24": [ + { "title": "Webmaster", "tier": 2 }, + { "title": "Open Source Software Co-TL", "tier": 33 }, + { "title": "Design Officer", "tier": 16 } + ], + "S25": [ + { "title": "Webmaster", "tier": 2 }, + { "title": "Open Source Software Co-TL", "tier": 33 } + ] + }, + "discord": "@semsema" + }, + { + "fullName": "Armanul Ambia", + "picture": "armanul-ambia.webp", + "positions": { + "S21": [{ "title": "Dev Officer", "tier": 19 }], + "F21": [{ "title": "Algo Director", "tier": 11 }], + "S22": [{ "title": "Algo President", "tier": 11 }], + "F22": [{ "title": "Algo Team Lead", "tier": 11 }], + "S23": [{ "title": "Algo Team Lead", "tier": 11 }] + } + }, + { + "fullName": "Gabriella Amedu", + "picture": "gabriella-amedu.webp", + "positions": { "S24": [{ "title": "AI Officer", "tier": 21 }] } + }, + { + "fullName": "Jason Anthony", + "picture": "jason-anthony.webp", + "positions": { "S21": [{ "title": "Secretary", "tier": 5 }] } + }, + { + "fullName": "Angel Armendariz", + "picture": "angel-armendariz.webp", + "positions": { "S22": [{ "title": "Dev Project Manager", "tier": 18 }] } + }, + { + "fullName": "Ryan Avancena", + "picture": "ryan-avancena.webp", + "positions": { + "S24": [{ "title": "AI Officer", "tier": 21 }], + "F24": [ + { "title": "AI Team Lead", "tier": 20 }, + { "title": "Marketing Officer", "tier": 9 } + ], + "S25": [{ "title": "AI Team Lead", "tier": 20 }] + }, + "discord": "@bergerboyyy" + }, + { + "fullName": "Sami Bajwa", + "picture": "sami-bajwa.webp", + "positions": { + "F21": [{ "title": "Node Buds Officer", "tier": 26 }], + "S22": [ + { "title": "Algo Officer", "tier": 12 }, + { "title": "Node Buds Officer", "tier": 26 } + ], + "F22": [{ "title": "Algo Officer", "tier": 12 }], + "S23": [{ "title": "Algo Officer", "tier": 12 }] + } + }, + { + "fullName": "Boushra Bettir", + "picture": "boushra-bettir.webp", + "positions": { + "F23": [{ "title": "Dev Officer", "tier": 19 }], + "S24": [{ "title": "Dev Officer", "tier": 19 }] + } + }, + { + "fullName": "Dane Camacho", + "picture": "dane-camacho.webp", + "positions": { + "F23": [{ "title": "Game Dev Officer", "tier": 23 }], + "S24": [{ "title": "Game Dev Officer", "tier": 23 }], + "F24": [{ "title": "Game Dev Team Lead", "tier": 22 }], + "S25": [{ "title": "Game Dev Team Lead", "tier": 22 }] + }, + "discord": "@daynger808" + }, + { + "fullName": "Jacob Carlson", + "picture": "jacob-carlson.webp", + "positions": { + "S23": [{ "title": "Game Dev Officer", "tier": 23 }], + "F23": [{ "title": "Design Officer", "tier": 16 }], + "S25": [{ "title": "Dev Officer", "tier": 19 }] + }, + "discord": "@jiink" + }, + { + "fullName": "Johnathan Carranza", + "picture": "johnathan-carranza.webp", + "positions": { "F21": [{ "title": "Dev Officer", "tier": 19 }] } + }, + { + "fullName": "Angel Cervantes", + "picture": "angel-cervantes.webp", + "positions": { + "S24": [{ "title": "Dev Officer", "tier": 19 }], + "F24": [ + { "title": "Algo Officer", "tier": 12 }, + { "title": "Node Buds Officer", "tier": 26 } + ], + "S25": [{ "title": "Algo Officer", "tier": 12 }] + }, + "discord": "@angellyn218" + }, + { + "fullName": "Ashley Chan", + "picture": "ashley-chan.webp", + "positions": { + "F23": [{ "title": "AI Officer", "tier": 21 }], + "S24": [{ "title": "Marketing Officer", "tier": 9 }], + "F24": [ + { "title": "Marketing Officer", "tier": 9 }, + { "title": "Node Buds Officer", "tier": 26 } + ], + "S25": [ + { "title": "Marketing Officer", "tier": 9 }, + { "title": "Node Buds Officer", "tier": 26 } + ] + }, + "discord": "@ashleychan_" + }, + { + "fullName": "Dulce Funez", + "picture": "dulce-funez.webp", + "positions": { "S24": [{ "title": "AI Officer", "tier": 21 }] } + }, + { + "fullName": "Wesley Chou", + "picture": "wesley-chou.webp", + "positions": { + "S21": [{ "title": "Dev Officer", "tier": 19 }], + "F21": [{ "title": "Dev Director", "tier": 17 }], + "S22": [{ "title": "Dev President", "tier": 17 }], + "F22": [{ "title": "Marketing Co-Lead", "tier": 8 }], + "S23": [{ "title": "Marketing Team Lead", "tier": 8 }] + } + }, + { + "fullName": "Oscar Cisneros", + "picture": "oscar-cisneros.webp", + "positions": { + "S23": [{ "title": "Dev Officer", "tier": 19 }], + "F23": [ + { "title": "Dev Officer", "tier": 19 }, + { "title": "Open Source Software Officer", "tier": 33 } + ], + "S24": [ + { "title": "Dev Project Manager", "tier": 18 }, + { "title": "Open Source Software Officer", "tier": 33 } + ] + } + }, + { + "fullName": "Jacob Corletto", + "picture": "jacob-corletto.webp", + "positions": { + "S24": [{ "title": "Marketing Officer", "tier": 9 }] + } + }, + { + "fullName": "Alan Cortez", + "picture": "alan-cortez.webp", + "positions": { + "S22": [{ "title": "Create Officer", "tier": 16 }], + "F22": [{ "title": "Design Team Lead", "tier": 13 }], + "S23": [{ "title": "Design Team Lead", "tier": 13 }], + "F23": [{ "title": "Design Team Lead", "tier": 13 }] + } + }, + { + "fullName": "Abel Daniel", + "picture": "placeholder.webp", + "positions": { + "F23": [{ "title": "Marketing Officer", "tier": 9 }], + "S24": [{ "title": "Marketing Officer", "tier": 9 }], + "F24": [{ "title": "Secretary", "tier": 5 }], + "S25": [ + { "title": "Secretary", "tier": 5 }, + { "title": "Node Buds Officer", "tier": 26 } + ] + }, + "discord": "@stable.orbit" + }, + { + "fullName": "Ethan Davidson", + "picture": "ethan-davidson.webp", + "positions": { + "S21": [{ "title": "Webmaster", "tier": 2 }], + "F21": [{ "title": "Webmaster", "tier": 2 }], + "S22": [{ "title": "Webmaster", "tier": 2 }], + "F22": [ + { "title": "Vice President", "tier": 1 }, + { "title": "Webmaster", "tier": 2 } + ], + "S23": [ + { "title": "Vice President", "tier": 1 }, + { "title": "Webmaster", "tier": 2 } + ], + "F23": [ + { "title": "Webmaster", "tier": 2 }, + { "title": "Open Source Software TL", "tier": 32 } + ], + "S24": [ + { "title": "Webmaster", "tier": 2 }, + { "title": "Open Source Software TL", "tier": 32 } + ] + } + }, + { + "fullName": "Kevin Dillon", + "picture": "kevin-dillon.webp", + "positions": { "S21": [{ "title": "Algo Officer", "tier": 12 }] } + }, + { + "fullName": "Diamond Dinh", + "picture": "diamond-dinh.webp", + "positions": { + "S22": [{ "title": "Dev Project Manager", "tier": 18 }], + "F22": [{ "title": "Special Events Officer", "tier": 25 }], + "S23": [{ "title": "Special Events Officer", "tier": 25 }], + "F23": [{ "title": "Dev Team Lead", "tier": 17 }], + "S24": [{ "title": "Dev Team Lead", "tier": 17 }] + } + }, + { + "fullName": "Randy Do", + "picture": "randy-do.webp", + "positions": { + "F23": [{ "title": "Algo Officer", "tier": 12 }], + "S24": [{ "title": "Algo Officer", "tier": 12 }], + "S25": [{ "title": "Algo Officer", "tier": 12 }] + }, + "discord": "@greenn_beann" + }, + { + "fullName": "Esteban Escartin", + "picture": "esteban-escartin.webp", + "positions": { + "F23": [{ "title": "Algo Officer", "tier": 12 }], + "S24": [{ "title": "Algo Officer", "tier": 12 }], + "F24": [{ "title": "Vice President", "tier": 1 }], + "S25": [{ "title": "Vice President", "tier": 1 }] + }, + "discord": "@pillo." + }, + { + "fullName": "Mark Ryan Garcia", + "picture": "mark-ryan-garcia.webp", + "positions": { + "S24": [{ "title": "Marketing Officer", "tier": 9 }], + "F24": [ + { "title": "Marketing Team Lead", "tier": 8 }, + { "title": "Node Buds Officer", "tier": 26 } + ], + "S25": [ + { "title": "Marketing Team Lead", "tier": 8 }, + { "title": "Game Dev Officer", "tier": 23 }, + { "title": "Node Buds Officer", "tier": 26 } + ] + }, + "discord": "@xnaym" + }, + { + "fullName": "Gabrielius Gintalas", + "picture": "gabrielius-gintalas.webp", + "positions": { + "S24": [{ "title": "Game Dev Officer", "tier": 23 }], + "F24": [{ "title": "Game Dev Officer", "tier": 23 }] + }, + "discord": "@gabeg" + }, + { + "fullName": "Nicholas Girmes", + "picture": "nick-girmes.webp", + "positions": { "S23": [{ "title": "Algo Officer", "tier": 12 }] } + }, + { + "fullName": "Eduardo Gomez", + "picture": "eduardo-gomez.webp", + "positions": { "S21": [{ "title": "Node Buds Officer", "tier": 26 }] } + }, + { + "fullName": "Nate Gries", + "picture": "nate-gries.webp", + "positions": { + "F23": [{ "title": "Algo Officer", "tier": 12 }], + "S24": [{ "title": "Algo Officer", "tier": 12 }], + "F24": [ + { "title": "Algo Team Lead", "tier": 11 }, + { "title": "Node Buds Officer", "tier": 26 } + ], + "S25": [{ "title": "Algo Team Lead", "tier": 11 }] + }, + "discord": "@nategries" + }, + { + "fullName": "Nurhaliza Hassan", + "picture": "nurhaliza-hassan.webp", + "positions": { + "S22": [{ "title": "Marketing Director", "tier": 7 }], + "F22": [{ "title": "Marketing Co-Lead", "tier": 8 }] + } + }, + { + "fullName": "Jeremiah Herring", + "picture": "jeremiah-herring.webp", + "positions": { + "S24": [{ "title": "Algo Officer", "tier": 12 }], + "F24": [ + { "title": "Algo Officer", "tier": 12 }, + { "title": "Open Source Software Officer", "tier": 33 } + ], + "S25": [{ "title": "Open Source Software Officer", "tier": 33 }] + }, + "discord": "@jerem2938" + }, + { + "fullName": "Kyle Ho", + "picture": "kyle-ho.webp", + "positions": { + "S24": [{ "title": "AI Officer", "tier": 21 }], + "F24": [{ "title": "AI Officer", "tier": 21 }], + "S25": [{ "title": "AI Officer", "tier": 21 }] + }, + "discord": "@kyle0532" + }, + { + "fullName": "Richard Hoang", + "picture": "richard-hoang.webp", + "positions": { + "S24": [{ "title": "Design Officer", "tier": 16 }], + "F24": [{ "title": "Design Officer", "tier": 16 }] + }, + "discord": "@renshintv" + }, + { + "fullName": "Lisa Hong", + "picture": "lisa-hong.webp", + "positions": { "S21": [{ "title": "Create Officer", "tier": 16 }] } + }, + { + "fullName": "Joshua Hughes", + "picture": "joshua-hughes.webp", + "positions": { "S21": [{ "title": "ICC Representative", "tier": 31 }] } + }, + { + "fullName": "Arish Imam", + "picture": "placeholder.webp", + "positions": { "F22": [{ "title": "AI Officer", "tier": 21 }] } + }, + { + "fullName": "Iftekharul Islam", + "picture": "iftekharul-islam.webp", + "positions": { "S22": [{ "title": "Algo Officer", "tier": 12 }] } + }, + { + "fullName": "Ibrahim Israr", + "picture": "ibrahim-israr.webp", + "positions": { "S22": [{ "title": "Secretary", "tier": 5 }] } + }, + { + "fullName": "Dave Javle", + "picture": "dave-javle.webp", + "positions": { + "S24": [{ "title": "Marketing Officer", "tier": 9 }], + "F24": [ + { "title": "Dev Officer", "tier": 19 }, + { "title": "Marketing Officer", "tier": 9 } + ], + "S25": [{ "title": "Dev Project Manager", "tier": 18 }] + }, + "discord": "@davejavle" + }, + { + "fullName": "Evan Jimenez", + "picture": "evan-jimenez.webp", + "positions": { + "S24": [{ "title": "Open Source Software Officer", "tier": 33 }], + "F24": [ + { "title": "Marketing Officer", "tier": 9 }, + { "title": "Open Source Software Officer", "tier": 33 } + ], + "S25": [{ "title": "Open Source Software Officer", "tier": 33 }] + }, + "discord": "@surrealreal_" + }, + { + "fullName": "Joel Anil John", + "picture": "joel-anil-john.webp", + "positions": { + "S22": [{ "title": "Dev Officer", "tier": 19 }], + "F22": [{ "title": "Algo Officer", "tier": 12 }], + "S23": [{ "title": "Algo Officer", "tier": 12 }], + "F23": [{ "title": "Algo Team Lead", "tier": 11 }], + "S24": [{ "title": "Algo Team Lead", "tier": 11 }] + } + }, + { + "fullName": "James Kim", + "picture": "james-kim.webp", + "positions": { + "F23": [{ "title": "Dev Officer", "tier": 19 }], + "S24": [{ "title": "Dev Officer", "tier": 19 }] + } + }, + { + "fullName": "Emily Krohn", + "picture": "emily-krohn.webp", + "positions": { + "S24": [{ "title": "Game Dev Officer", "tier": 23 }], + "F24": [{ "title": "Game Dev Officer", "tier": 23 }] + }, + "discord": "@emilyk23" + }, + { + "fullName": "Ashley Kuewa", + "picture": "ashley-kuewa.webp", + "positions": { "S23": [{ "title": "Marketing Officer", "tier": 9 }] } + }, + { + "fullName": "Rohan Kunchala", + "picture": "rohan-kunchala.webp", + "positions": { + "F22": [{ "title": "Algo Officer", "tier": 12 }], + "S23": [{ "title": "Algo Officer", "tier": 12 }], + "F23": [{ "title": "Algo Officer", "tier": 12 }] + } + }, + { + "fullName": "Andy Lasso", + "picture": "andy-lasso.webp", + "positions": { "F21": [{ "title": "Dev Officer", "tier": 19 }] } + }, + { + "fullName": "Logan Langdon", + "picture": "placeholder.webp", + "positions": { + "S23": [{ "title": "Game Dev Officer", "tier": 23 }], + "F23": [{ "title": "Game Dev Team Lead", "tier": 22 }], + "S24": [{ "title": "Game Dev Team Lead", "tier": 22 }] + } + }, + { + "fullName": "Andrew Lau", + "picture": "andrew-lau.webp", + "positions": { "S21": [{ "title": "Treasurer", "tier": 4 }] } + }, + { + "fullName": "Matthew Lau", + "picture": "placeholder.webp", + "positions": { + "F23": [{ "title": "Marketing Officer", "tier": 9 }], + "S24": [{ "title": "Marketing Officer", "tier": 9 }], + "F24": [{ "title": "Node Buds Officer", "tier": 26 }] + }, + "discord": "@.cheesee." + }, + { + "fullName": "Yulie Ledesma", + "picture": "yulie-ledesma.webp", + "positions": { + "F23": [{ "title": "Design Officer", "tier": 16 }], + "S24": [{ "title": "Design Officer", "tier": 16 }], + "F24": [{ "title": "Design Officer", "tier": 16 }], + "S25": [{ "title": "Design Officer", "tier": 16 }] + }, + "discord": "@vroyuh" + }, + { + "fullName": "Kenny Le", + "picture": "placeholder.webp", + "positions": { "S24": [{ "title": "Marketing Officer", "tier": 9 }] } + }, + { + "fullName": "Minh Le", + "picture": "minh-le.webp", + "positions": { "S22": [{ "title": "Dev Officer", "tier": 19 }] } + }, + { + "fullName": "Nguyen Le", + "picture": "nguyen-le.webp", + "positions": { "S22": [{ "title": "Data Analyst", "tier": 7 }] } + }, + { + "fullName": "Tommy Le", + "picture": "tommy-le.webp", + "positions": { + "F21": [{ "title": "Treasurer", "tier": 4 }], + "S22": [{ "title": "Treasurer", "tier": 4 }] + } + }, + { + "fullName": "Eugene Lee", + "picture": "eugene-lee.webp", + "positions": { + "S21": [{ "title": "Node Buds Officer", "tier": 26 }], + "F21": [{ "title": "Node Buds Officer", "tier": 26 }], + "S22": [{ "title": "Node Buds Officer", "tier": 26 }] + } + }, + { + "fullName": "Joe Lee", + "picture": "joe-lee.webp", + "positions": { + "S23": [{ "title": "Dev Officer", "tier": 19 }], + "F23": [{ "title": "Algo Officer", "tier": 12 }], + "S24": [{ "title": "Design Team Lead", "tier": 13 }] + } + }, + { + "fullName": "Nolan Lee", + "picture": "nolan-lee.webp", + "positions": { + "S22": [{ "title": "Historian", "tier": 7 }], + "S24": [{ "title": "Marketing Officer", "tier": 9 }] + } + }, + { + "fullName": "Aaron Lieberman", + "picture": "aaron-lieberman.webp", + "positions": { + "S21": [{ "title": "Internal Vice President, Node Buds Officer", "tier": 1 }], + "F21": [{ "title": "President, Node Buds Officer", "tier": 0 }], + "S22": [{ "title": "President, Node Buds Officer", "tier": 0 }], + "F22": [{ "title": "Special Events Team Lead", "tier": 24 }], + "S23": [{ "title": "Special Events Team Lead", "tier": 24 }] + } + }, + { + "fullName": "Yao Lin", + "picture": "yao-lin.webp", + "positions": { "F22": [{ "title": "AI Officer", "tier": 21 }] } + }, + { + "fullName": "Eric Ly", + "picture": "eric-ly.webp", + "positions": { + "F23": [{ "title": "Treasurer", "tier": 4 }], + "S24": [{ "title": "Treasurer", "tier": 4 }], + "F24": [ + { "title": "Node Buds Team Lead", "tier": 34 }, + { "title": "AI Officer", "tier": 21 } + ], + "S25": [{ "title": "Node Buds Officer", "tier": 26 }] + }, + "discord": "@lyyeric" + }, + { + "fullName": "Shaleen Mathur", + "picture": "shaleen-mathur.webp", + "positions": { "S21": [{ "title": "Workshop Manager", "tier": 28 }] } + }, + { + "fullName": "Elena Marquez", + "picture": "elena-marquez.webp", + "positions": { + "F23": [{ "title": "Event Coordinator", "tier": 6 }], + "S24": [ + { "title": "Event Coordinator", "tier": 6 }, + { "title": "Marketing Officer", "tier": 9 } + ], + "F24": [ + { "title": "Event Coordinator", "tier": 6 }, + { "title": "Marketing Officer", "tier": 9 }, + { "title": "Node Buds Co-Team Lead", "tier": 36 } + ], + "S25": [ + { "title": "Event Coordinator", "tier": 6 }, + { "title": "Node Buds Team Lead", "tier": 34 } + ] + }, + "discord": "@evie724" + }, + { + "fullName": "Ean McGilvery", + "picture": "ean-mcgilvery.webp", + "positions": { "S21": [{ "title": "Node Buds Officer", "tier": 26 }] } + }, + { + "fullName": "Jorge Mejia", + "picture": "jorge-mejia.webp", + "positions": { + "F21": [{ "title": "Dev Officer", "tier": 19 }], + "S22": [{ "title": "Dev Officer", "tier": 19 }], + "F22": [{ "title": "Design Officer", "tier": 16 }], + "S23": [{ "title": "Design Officer", "tier": 16 }] + } + }, + { + "fullName": "Brian Milian", + "picture": "brian-milian.webp", + "positions": { + "S23": [{ "title": "Marketing Officer", "tier": 9 }], + "F23": [ + { "title": "Marketing Team Lead", "tier": 8 }, + { "title": "Algo Officer", "tier": 12 } + ], + "S24": [ + { "title": "Marketing Team Lead", "tier": 8 }, + { "title": "Algo Officer", "tier": 12 } + ] + } + }, + { + "fullName": "Jordan Muir", + "picture": "jordan-muir.webp", + "positions": { + "S23": [{ "title": "Game Dev Officer", "tier": 23 }], + "F23": [{ "title": "Game Dev Officer", "tier": 23 }], + "S24": [{ "title": "Game Dev Officer", "tier": 23 }], + "F24": [{ "title": "Game Dev Officer", "tier": 23 }] + }, + "discord": "@muirror" + }, + { + "fullName": "Tanisha Naik", + "picture": "tanisha-naik.webp", + "positions": { + "S23": [{ "title": "Design Officer", "tier": 16 }], + "F23": [{ "title": "Design Officer", "tier": 16 }], + "S24": [{ "title": "Design Officer", "tier": 16 }], + "F24": [{ "title": "Design Team Lead", "tier": 13 }], + "S25": [{ "title": "Design Team Lead", "tier": 13 }] + }, + "discord": "@t_1907" + }, + { + "fullName": "Serena Naranjo", + "picture": "serena-naranjo.webp", + "positions": { + "F21": [{ "title": "Create Officer", "tier": 16 }], + "S22": [{ "title": "Create Officer", "tier": 16 }] + } + }, + { + "fullName": "Joseph Nasr", + "picture": "joseph-nasr.webp", + "positions": { "S23": [{ "title": "Game Dev Officer", "tier": 23 }] } + }, + { + "fullName": "Alexis Nemsingh", + "picture": "alexis-nemsingh.webp", + "positions": { + "S24": [{ "title": "Marketing Officer", "tier": 9 }] + }, + "discord": "@redsplash1" + }, + { + "fullName": "Dalisa Nguyen", + "picture": "dalisa-nguyen.webp", + "positions": { "S21": [{ "title": "Node Buds Officer", "tier": 26 }] } + }, + { + "fullName": "Kayla Nguyen", + "picture": "kayla-nguyen.webp", + "positions": { "F21": [{ "title": "Create Officer", "tier": 16 }] } + }, + { + "fullName": "Jacob Nguyen", + "picture": "jacob-nguyen.webp", + "positions": { + "S21": [{ "title": "President, Create Director, Node Buds Officer", "tier": 0 }], + "F22": [{ "title": "Treasurer", "tier": 4 }], + "S23": [{ "title": "Treasurer", "tier": 4 }] + } + }, + { + "fullName": "Ngoc Nguyen", + "picture": "ngoc-nguyen.webp", + "positions": { "F23": [{ "title": "AI Officer", "tier": 21 }] } + }, + { + "fullName": "Taylor Noh", + "picture": "taylor-noh.webp", + "positions": { + "S21": [{ "title": "Node Buds Officer", "tier": 26 }], + "F21": [{ "title": "Node Buds Officer", "tier": 26 }], + "S22": [{ "title": "Node Buds Officer", "tier": 26 }] + } + }, + { + "fullName": "Kirsten Ochoa", + "picture": "kirsten-ochoa.webp", + "positions": { + "S22": [{ "title": "Create Project Developer", "tier": 14 }], + "F22": [{ "title": "Design Officer", "tier": 16 }] + } + }, + { + "fullName": "Tomas Oh", + "picture": "tomas-oh.webp", + "positions": { + "S24": [{ "title": "Open Source Software Officer", "tier": 33 }], + "F24": [{ "title": "Open Source Software Team Lead", "tier": 32 }], + "S25": [{ "title": "Open Source Software Team Lead", "tier": 32 }] + }, + "discord": "@chomacito" + }, + { + "fullName": "Oyinkansola Olayinka", + "picture": "oyinkansola-olayinka.webp", + "positions": { + "F23": [{ "title": "Marketing Officer", "tier": 9 }], + "S24": [{ "title": "Marketing Officer", "tier": 9 }] + } + }, + { + "fullName": "Amy Parker", + "picture": "amy-parker.webp", + "positions": { + "F23": [{ "title": "Dev Officer", "tier": 19 }], + "S24": [{ "title": "Dev Officer", "tier": 19 }], + "F24": [{ "title": "Dev Team Lead", "tier": 17 }], + "S25": [{ "title": "Dev Team Lead", "tier": 17 }] + }, + "discord": "@amyipdev" + }, + { + "fullName": "Mike Ploythai", + "picture": "mike-ploythai.webp", + "positions": { + "S21": [{ "title": "Create Officer", "tier": 16 }], + "F21": [{ "title": "Create Director, Marketing Chair", "tier": 13 }], + "S22": [{ "title": "Create President", "tier": 13 }] + } + }, + { + "fullName": "Stephanie Pocci", + "picture": "stephanie-pocci.webp", + "positions": { + "S22": [{ "title": "Event Coordinator", "tier": 6 }], + "F22": [{ "title": "Event Coordinator", "tier": 6 }], + "S23": [{ "title": "Game Dev Team Lead", "tier": 22 }] + } + }, + { + "fullName": "Angela Queano", + "picture": "angela-queano.webp", + "positions": { "S23": [{ "title": "Marketing Officer", "tier": 9 }] } + }, + { + "fullName": "Nithin Rajesh", + "picture": "nithin-rajesh.webp", + "positions": { + "S24": [{ "title": "Game Dev Officer", "tier": 23 }], + "F24": [{ "title": "Game Dev Officer", "tier": 23 }], + "S25": [{ "title": "Game Dev Officer", "tier": 23 }] + }, + "discord": "@ok7317" + }, + { + "fullName": "Alejandro Ramos", + "picture": "alejandro-ramos.webp", + "positions": { + "F22": [{ "title": "Design Officer", "tier": 16 }], + "S23": [{ "title": "Design Officer", "tier": 16 }] + } + }, + { + "fullName": "Nicolas Renteria", + "picture": "nicolas-renteria.webp", + "positions": { "S21": [{ "title": "Marketing Chair", "tier": 7 }] } + }, + { + "fullName": "Joel Daniel Rico", + "picture": "joel-daniel-rico.webp", + "positions": { + "F23": [{ "title": "AI Officer", "tier": 21 }], + "S24": [{ "title": "Dev Officer", "tier": 19 }], + "F24": [{ "title": "President", "tier": 0 }], + "S25": [{ "title": "President", "tier": 0 }] + }, + "discord": "@jjoeldaniel" + }, + { + "fullName": "Max Rivas", + "picture": "max-rivas.webp", + "positions": { + "F23": [{ "title": "Marketing Officer", "tier": 9 }], + "S24": [{ "title": "Marketing Officer", "tier": 9 }], + "F24": [ + { "title": "Event Coordinator", "tier": 6 }, + { "title": "Node Buds Officer", "tier": 26 } + ], + "S25": [ + { "title": "Event Coordinator", "tier": 6 }, + { "title": "Node Buds Co-Team Lead", "tier": 36 } + ] + }, + "discord": "@meexy23" + }, + { + "fullName": "Wilbert Rodriguez", + "picture": "wilbert-rodriguez.webp", + "positions": { "S21": [{ "title": "Intern Program Manager", "tier": 29 }] } + }, + { + "fullName": "Vanessa Roque", + "picture": "vanessa-roque.webp", + "positions": { "S23": [{ "title": "AI Officer", "tier": 21 }] } + }, + { + "fullName": "Ashley Rus", + "picture": "placeholder.webp", + "positions": { + "S23": [{ "title": "AI Officer", "tier": 21 }], + "F23": [{ "title": "AI Team Lead", "tier": 20 }], + "S24": [{ "title": "AI Team Lead", "tier": 20 }] + } + }, + { + "fullName": "Sara Sadek", + "picture": "placeholder.webp", + "positions": { "F22": [{ "title": "AI Officer", "tier": 21 }] } + }, + { + "fullName": "Samuel Sandoval", + "picture": "samuel-sandoval.webp", + "positions": { + "S21": [ + { "title": "Vice President", "tier": 1 }, + { "title": "Dev Director", "tier": 17 } + ] + } + }, + { + "fullName": "Kavit Sanghavi", + "picture": "kavit-sanghavi.webp", + "positions": { "S21": [{ "title": "Algo Director", "tier": 11 }] } + }, + { + "fullName": "Angel Santoyo", + "picture": "angel-santoyo.webp", + "positions": { + "F22": [{ "title": "Dev Officer", "tier": 19 }], + "S23": [{ "title": "Dev Officer", "tier": 19 }], + "F23": [{ "title": "Dev Officer", "tier": 19 }] + } + }, + { + "fullName": "Sebastian Serrano", + "picture": "sebastian-serrano.webp", + "positions": { + "F23": [{ "title": "AI Officer", "tier": 21 }], + "S24": [{ "title": "AI Officer", "tier": 21 }] + } + }, + { + "fullName": "Parth Sharma", + "picture": "parth-sharma.webp", + "positions": { "S21": [{ "title": "Algo Officer", "tier": 12 }] } + }, + { + "fullName": "Patrick Smith", + "picture": "patrick-smith.webp", + "positions": { + "F23": [{ "title": "Algo Officer", "tier": 12 }], + "S24": [ + { "title": "Algo Officer", "tier": 12 }, + { "title": "Marketing Officer", "tier": 9 } + ], + "F24": [ + { "title": "Algo Officer", "tier": 12 }, + { "title": "Node Buds Officer", "tier": 26 } + ], + "S25": [ + { "title": "Algo Officer", "tier": 12 }, + { "title": "Node Buds Officer", "tier": 26 } + ] + }, + "discord": "@pattyrick1" + }, + { + "fullName": "David Solano", + "picture": "david-solano.webp", + "positions": { + "S23": [{ "title": "Algo Officer", "tier": 12 }], + "F23": [ + { "title": "President", "tier": 0 }, + { "title": "Algo Officer", "tier": 12 } + ], + "S24": [ + { "title": "President", "tier": 0 }, + { "title": "Algo Officer", "tier": 12 } + ], + "F24": [ + { "title": "Treasurer", "tier": 4 }, + { "title": "Algo Officer", "tier": 12 }, + { "title": "Node Buds Officer", "tier": 26 } + ], + "S25": [ + { "title": "Treasurer", "tier": 4 }, + { "title": "Algo Officer", "tier": 12 } + ] + }, + "discord": "@davidjsol" + }, + { + "fullName": "James Owen Sterling", + "picture": "james-owen-sterling.webp", + "positions": { + "S24": [{ "title": "Open Source Software Officer", "tier": 33 }], + "F24": [{ "title": "Open Source Software Officer", "tier": 33 }] + }, + "discord": "@typos." + }, + { + "fullName": "Justin Stitt", + "picture": "justin-stitt.webp", + "positions": { + "S22": [{ "title": "Algo Officer", "tier": 12 }], + "F22": [{ "title": "AI Team Lead", "tier": 20 }], + "S23": [{ "title": "AI Team Lead", "tier": 20 }] + } + }, + { + "fullName": "Joann Sum", + "picture": "joann-sum.webp", + "positions": { + "S23": [{ "title": "Marketing Officer", "tier": 9 }], + "F24": [ + { "title": "Algo Officer", "tier": 12 }, + { "title": "AI Officer", "tier": 21 } + ], + "S25": [ + { "title": "Algo Officer", "tier": 12 }, + { "title": "AI Officer", "tier": 21 } + ] + }, + "discord": "@josu4412" + }, + { + "fullName": "Charlie Taylor", + "picture": "charlie-taylor.webp", + "positions": { + "F22": [{ "title": "Dev Team Lead", "tier": 17 }], + "S23": [{ "title": "Dev Team Lead", "tier": 17 }] + } + }, + { + "fullName": "Johnson Tong", + "picture": "johnson-tong.webp", + "positions": { "S21": [{ "title": "Workshop Manager", "tier": 28 }] } + }, + { + "fullName": "Alex Tran", + "picture": "alex-tran.webp", + "positions": { "F23": [{ "title": "Game Dev Officer", "tier": 23 }] } + }, + { + "fullName": "Alex Truong", + "picture": "alex-truong.webp", + "positions": { "F21": [{ "title": "Algo Officer", "tier": 12 }] } + }, + { + "fullName": "Daniel Truong", + "picture": "daniel-truong.webp", + "positions": { + "S22": [{ "title": "Dev Project Manager", "tier": 18 }], + "F22": [{ "title": "Dev Project Manager", "tier": 18 }], + "S23": [{ "title": "Dev Project Manager", "tier": 18 }], + "F23": [{ "title": "Vice President", "tier": 1 }], + "S24": [ + { "title": "Vice President", "tier": 1 }, + { "title": "AI Officer", "tier": 21 } + ] + } + }, + { + "fullName": "Samuel Valls", + "picture": "samuel-valls.webp", + "positions": { + "S21": [{ "title": "Community Manager", "tier": 30 }], + "F21": [{ "title": "Create Officer", "tier": 16 }], + "S22": [{ "title": "Create Operations Manager", "tier": 15 }] + } + }, + { + "fullName": "Anthony Vences", + "picture": "anthony-vences.webp", + "positions": { + "F23": [{ "title": "AI Officer", "tier": 21 }], + "S24": [{ "title": "AI Officer", "tier": 21 }] + } + }, + { + "fullName": "Cisco Velasquez", + "picture": "cisco-velasquez.webp", + "positions": { + "S24": [{ "title": "Marketing Officer", "tier": 9 }] + } + }, + { + "fullName": "Karnikaa Velumani", + "picture": "karnikaa-velumani.webp", + "positions": { + "F21": [{ "title": "Vice President", "tier": 1 }], + "S22": [{ "title": "Vice President", "tier": 1 }], + "F22": [{ "title": "President", "tier": 0 }], + "S23": [{ "title": "President", "tier": 0 }], + "F23": [{ "title": "Open Source Software Co-TL", "tier": 32 }], + "S24": [{ "title": "Open Source Software Co-TL", "tier": 32 }] + } + }, + { + "fullName": "Kinsey Vo", + "picture": "kinsey-vo.webp", + "positions": { + "F23": [{ "title": "Marketing Officer", "tier": 9 }], + "S24": [{ "title": "Marketing Officer", "tier": 9 }] + } + }, + { + "fullName": "Rina Watanabe", + "picture": "rina-watanabe.webp", + "positions": { "F21": [{ "title": "Dev Project Manager", "tier": 18 }] } + }, + { + "fullName": "Kyle Whynott", + "picture": "kyle-whynott.webp", + "positions": { + "F23": [{ "title": "Secretary", "tier": 5 }], + "S24": [{ "title": "Secretary", "tier": 5 }] + } + }, + { + "fullName": "Jason Wong", + "picture": "jason-wong.webp", + "positions": { + "S22": [{ "title": "Historian", "tier": 7 }], + "F22": [{ "title": "Marketing Officer", "tier": 8 }] + } + }, + { + "fullName": "Cyril Youssef", + "picture": "cyril-youssef.webp", + "positions": { + "S23": [{ "title": "Algo Officer", "tier": 12 }], + "F23": [{ "title": "Algo Officer", "tier": 12 }], + "S24": [{ "title": "Algo Officer", "tier": 12 }], + "F24": [{ "title": "Algo Officer", "tier": 12 }], + "S25": [{ "title": "Algo Officer", "tier": 12 }] + }, + "discord": "@cyrily" + }, + { + "fullName": "Alexander Zavaleta", + "picture": "alexander-zavaleta.webp", + "positions": { + "S23": [{ "title": "AI Officer", "tier": 21 }], + "F23": [{ "title": "AI Officer", "tier": 21 }] + } + }, + { + "fullName": "Nick Goulart", + "picture": "nick-goulart.webp", + "positions": { + "F24": [ + { "title": "Dev Officer", "tier": 19 }, + { "title": "Server Team Member", "tier": 35 } + ] + }, + "discord": "@realbignick" + }, + { + "fullName": "Sarah Agnihotri", + "picture": "sarah-agnihotri.webp", + "positions": { + "F24": [{ "title": "AI Officer", "tier": 21 }] + }, + "discord": "@sarah8r" + }, + { + "fullName": "Yves Velasquez Vega", + "picture": "yves-velasquez-vega.webp", + "positions": { + "F24": [{ "title": "AI Officer", "tier": 21 }] + }, + "discord": "@hallowsyves" + }, + { + "fullName": "Teresa To", + "picture": "teresa-to.webp", + "positions": { + "F24": [{ "title": "Dev Officer", "tier": 19 }] + }, + "discord": "@craziderpy" + }, + { + "fullName": "Mariia Grushina", + "picture": "mariia-grushina.webp", + "positions": { + "F24": [{ "title": "Algo Officer", "tier": 12 }], + "S25": [{ "title": "Algo Officer", "tier": 12 }] + }, + "discord": "@marynash_" + }, + { + "fullName": "Braedon Collet", + "picture": "placeholder.webp", + "positions": { + "F24": [{ "title": "AI Officer", "tier": 21 }], + "S25": [{ "title": "AI Officer", "tier": 21 }] + }, + "discord": "@python.programmer" + }, + { + "fullName": "Tyler Lui", + "picture": "tyler-lui.webp", + "positions": { + "F24": [{ "title": "Open Source Software Officer", "tier": 33 }], + "S25": [{ "title": "Open Source Software Officer", "tier": 33 }] + }, + "discord": "@slat._" + }, + { + "fullName": "Kenny Garcia", + "picture": "kenny-garcia.webp", + "positions": { + "F24": [{ "title": "AI Officer", "tier": 21 }], + "S25": [{ "title": "AI Officer", "tier": 21 }] + }, + "discord": "@kenny8092" + }, + { + "fullName": "Elizabeth Mazuca", + "picture": "elizabeth-mazuca.webp", + "positions": { + "F24": [{ "title": "Design Officer", "tier": 16 }] + }, + "discord": "@elzies" + }, + { + "fullName": "Chris Liwanag", + "picture": "chris-liwanag.webp", + "positions": { + "F24": [{ "title": "Game Dev Officer", "tier": 23 }], + "S25": [{ "title": "Game Dev Officer", "tier": 23 }] + }, + "discord": "@quisyo" + }, + { + "fullName": "Syon Chau", + "picture": "syon-chau.webp", + "positions": { + "F24": [{ "title": "Dev Officer", "tier": 19 }] + }, + "discord": "@poe" + }, + { + "fullName": "Kourosh Alasti", + "picture": "kourosh-alasti.webp", + "positions": { + "F24": [{ "title": "Dev Officer", "tier": 19 }], + "S25": [{ "title": "Dev Officer", "tier": 19 }] + }, + "discord": "@alastiaf" + }, + { + "fullName": "Katherine Sutandar", + "picture": "katherine-sutandar.webp", + "positions": { + "F24": [{ "title": "Game Dev Officer", "tier": 23 }] + }, + "discord": "@kathoy" + }, + { + "fullName": "Alexander Peras", + "picture": "alexander-peras.webp", + "positions": { + "F24": [{ "title": "Open Source Software Officer", "tier": 33 }], + "S25": [{ "title": "Open Source Software Officer", "tier": 33 }] + }, + "discord": "@drafte" + }, + { + "fullName": "Jeffrey Rivera", + "picture": "jeffrey-rivera.webp", + "positions": { + "F24": [{ "title": "Design Officer", "tier": 16 }], + "S25": [{ "title": "Design Officer", "tier": 16 }] + }, + "discord": "@cheppies" + }, + { + "fullName": "Hoang Nguyen", + "picture": "hoang-nguyen.webp", + "positions": { + "F24": [{ "title": "Dev Officer", "tier": 19 }] + }, + "discord": "@__panda3004" + }, + { + "fullName": "Ahad Munir Ahmad", + "picture": "ahad-munir-ahmad.webp", + "positions": { + "F24": [{ "title": "Dev Project Manager", "tier": 18 }], + "S25": [{ "title": "Dev Officer", "tier": 19 }] + }, + "discord": "@adj1200" + }, + { + "fullName": "A.J. Galliguez", + "picture": "aj-galliguez.webp", + "positions": { + "F24": [{ "title": "Marketing Officer", "tier": 9 }], + "S25": [ + { "title": "Design Officer", "tier": 16 }, + { "title": "Marketing Officer", "tier": 9 } + ] + }, + "discord": "@ajareyouthere" + }, + { + "fullName": "Noah Scott", + "picture": "noah-scott.webp", + "positions": { + "F24": [{ "title": "Marketing Officer", "tier": 9 }], + "S25": [ + { "title": "Algo Officer", "tier": 12 }, + { "title": "Marketing Officer", "tier": 9 } + ] + }, + "discord": "@0100.1110" + }, + { + "fullName": "Hannah Minji Park", + "picture": "hannah-minji-park.webp", + "positions": { + "F24": [{ "title": "Marketing Officer", "tier": 9 }], + "S25": [{ "title": "Marketing Officer", "tier": 9 }] + }, + "discord": "@wiseeisw_99756" + }, + { + "fullName": "Isla Kim", + "picture": "isla-kim.webp", + "positions": { + "S25": [{ "title": "Open Source Software Officer", "tier": 33 }] + }, + "discord": "@chanran_1229" + }, + { + "fullName": "Siddharth Vasu", + "picture": "siddharth-vasu.webp", + "positions": { + "S25": [{ "title": "Open Source Software Officer", "tier": 33 }] + }, + "discord": "@prince_sidon" + }, + { + "fullName": "Nestor Reategui", + "picture": "nestor-reategui.webp", + "positions": { + "S25": [{ "title": "Open Source Software Officer", "tier": 33 }] + }, + "discord": "@nestiray" + }, + { + "fullName": "Joshua Holman", + "picture": "joshua-holman.webp", + "positions": { + "S25": [{ "title": "Open Source Software Officer", "tier": 33 }] + }, + "discord": "@thejolman" + }, + { + "fullName": "Talhah Raheem", + "picture": "talhah-raheem.webp", + "positions": { + "S25": [{ "title": "Dev Officer", "tier": 19 }] + }, + "discord": "@arrow5566" + }, + { + "fullName": "Kevin Geier-Conney", + "picture": "kevin-geier-conney.webp", + "positions": { + "S25": [{ "title": "Algo Officer", "tier": 12 }] + }, + "discord": "@iunk" + }, + { + "fullName": "Christian Huerta", + "picture": "christian-huerta.webp", + "positions": { + "S25": [{ "title": "Design Officer", "tier": 16 }] + }, + "discord": "@swiftpup121" + }, + { + "fullName": "Timothy Ou", + "picture": "timothy-ou.webp", + "positions": { + "S25": [{ "title": "Marketing Officer", "tier": 9 }] + }, + "discord": "@timothyou" + }, + { + "fullName": "Gage Ashbaugh", + "picture": "gage-ashbaugh.webp", + "positions": { + "S25": [{ "title": "Marketing Officer", "tier": 9 }] + }, + "discord": "@ashbox" + }, + { + "fullName": "Nathan Choi", + "picture": "nathan-choi.webp", + "positions": { + "S25": [{ "title": "AI Officer", "tier": 21 }] + }, + "discord": "@nchoi123" + }, + { + "fullName": "Eileen Nguyen", + "picture": "eileen-nguyen.webp", + "positions": { + "S25": [{ "title": "Marketing Officer", "tier": 9 }] + }, + "discord": "@tealixia" + }, + { + "fullName": "Ender Batu Demirtaş", + "picture": "ender-batu.webp", + "positions": { + "S25": [{ "title": "AI Officer", "tier": 21 }] + }, + "discord": "@rothchild" + }, + { + "fullName": "Madeline Savoiu", + "picture": "madeline-savoiu.webp", + "positions": { + "S25": [{ "title": "Marketing Officer", "tier": 9 }] + }, + "discord": "@briuche" + }, + { + "fullName": "Sachin Lodhi", + "picture": "sachin-lodhi.webp", + "positions": { + "S25": [{ "title": "AI Officer", "tier": 21 }] + }, + "discord": "@teradyne_" + }, + { + "fullName": "Angel Gaspar", + "picture": "angel-gaspar.webp", + "positions": { + "S25": [{ "title": "Marketing Officer", "tier": 9 }] + }, + "discord": "@qasple" + }, + { + "fullName": "Demi Chen", + "picture": "demi-chen.webp", + "positions": { + "S25": [{ "title": "Node Buds Officer", "tier": 26 }] + }, + "discord": "@hungry52" + } + ] \ No newline at end of file From deb66d434457f71a35713ee235ab18d2a3c725d7 Mon Sep 17 00:00:00 2001 From: josh Date: Wed, 29 Oct 2025 08:27:46 -0700 Subject: [PATCH 03/13] formatting --- init_db.go | 168 ++++++++++++++++++++++++++--------------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/init_db.go b/init_db.go index 0edcacf..e322622 100644 --- a/init_db.go +++ b/init_db.go @@ -1,45 +1,45 @@ package main import ( - "database/sql" - "encoding/json" - "fmt" - "log" - "os" - "strings" - _ "github.com/mattn/go-sqlite3" + "database/sql" + "encoding/json" + "fmt" + _ "github.com/mattn/go-sqlite3" + "log" + "os" + "strings" ) type Officer struct { - FullName string `json:"fullName"` - Picture string `json:"picture"` - Positions map[string][]struct { - Title string `json:"title"` - Tier int `json:"tier"` - } `json:"positions"` - Discord string `json:"discord,omitempty"` + FullName string `json:"fullName"` + Picture string `json:"picture"` + Positions map[string][]struct { + Title string `json:"title"` + Tier int `json:"tier"` + } `json:"positions"` + Discord string `json:"discord,omitempty"` } func main() { - data, err := os.ReadFile("officers.json") - if err != nil { - log.Fatal("Error reading JSON file:", err) - } + data, err := os.ReadFile("officers.json") + if err != nil { + log.Fatal("Error reading JSON file:", err) + } - var officers []Officer - err = json.Unmarshal(data, &officers) - if err != nil { - log.Fatal("Error unmarshaling JSON:", err) - } + var officers []Officer + err = json.Unmarshal(data, &officers) + if err != nil { + log.Fatal("Error unmarshaling JSON:", err) + } - db, err := sql.Open("sqlite3", "./dev.db") - if err != nil { - log.Fatal("Error opening database:", err) - } - defer db.Close() + db, err := sql.Open("sqlite3", "./dev.db") + if err != nil { + log.Fatal("Error opening database:", err) + } + defer db.Close() // Create tables if they don't exist - _, err = db.Exec(` + _, err = db.Exec(` CREATE TABLE IF NOT EXISTS officer ( uuid CHAR(4) PRIMARY KEY, full_name VARCHAR(30) NOT NULL, @@ -67,67 +67,67 @@ func main() { CONSTRAINT fk_tiers FOREIGN KEY (tier) REFERENCES tier(tier) ); `) - if err != nil { - log.Fatal("Error creating tables:", err) - } + if err != nil { + log.Fatal("Error creating tables:", err) + } // Prepare statements - officerStmt, err := db.Prepare("INSERT OR IGNORE INTO officer (uuid, full_name, picture, discord) VALUES (?, ?, ?, ?)") - if err != nil { - log.Fatal("Error preparing officer statement:", err) - } - defer officerStmt.Close() + officerStmt, err := db.Prepare("INSERT OR IGNORE INTO officer (uuid, full_name, picture, discord) VALUES (?, ?, ?, ?)") + if err != nil { + log.Fatal("Error preparing officer statement:", err) + } + defer officerStmt.Close() - tierStmt, err := db.Prepare("INSERT OR IGNORE INTO tier (tier, title) VALUES (?, ?)") - if err != nil { - log.Fatal("Error preparing tier statement:", err) - } - defer tierStmt.Close() + tierStmt, err := db.Prepare("INSERT OR IGNORE INTO tier (tier, title) VALUES (?, ?)") + if err != nil { + log.Fatal("Error preparing tier statement:", err) + } + defer tierStmt.Close() - positionStmt, err := db.Prepare("INSERT OR IGNORE INTO position (oid, semester, tier, full_name, title) VALUES (?, ?, ?, ?, ?)") - if err != nil { - log.Fatal("Error preparing position statement:", err) - } - defer positionStmt.Close() + positionStmt, err := db.Prepare("INSERT OR IGNORE INTO position (oid, semester, tier, full_name, title) VALUES (?, ?, ?, ?, ?)") + if err != nil { + log.Fatal("Error preparing position statement:", err) + } + defer positionStmt.Close() - // Insert officers - for i, officer := range officers { - // Generate sequential 4-digit ID (0001, 0002, etc.) - sequentialID := fmt.Sprintf("%04d", i+1) + // Insert officers + for i, officer := range officers { + // Generate sequential 4-digit ID (0001, 0002, etc.) + sequentialID := fmt.Sprintf("%04d", i+1) - // Insert officer - _, err = officerStmt.Exec(sequentialID, officer.FullName, officer.Picture, officer.Discord) - if err != nil { - log.Printf("Error inserting officer %s: %v", officer.FullName, err) - continue - } + // Insert officer + _, err = officerStmt.Exec(sequentialID, officer.FullName, officer.Picture, officer.Discord) + if err != nil { + log.Printf("Error inserting officer %s: %v", officer.FullName, err) + continue + } - // Insert all positions the officer holds - for semester, positions := range officer.Positions { - for _, pos := range positions { - // Insert tier if it doesn't exist yet - _, err = tierStmt.Exec(pos.Tier, pos.Title) - if err != nil { - log.Printf("Error inserting tier %d for officer %s: %v", pos.Tier, officer.FullName, err) - continue - } + // Insert all positions the officer holds + for semester, positions := range officer.Positions { + for _, pos := range positions { + // Insert tier if it doesn't exist yet + _, err = tierStmt.Exec(pos.Tier, pos.Title) + if err != nil { + log.Printf("Error inserting tier %d for officer %s: %v", pos.Tier, officer.FullName, err) + continue + } - // Insert position - _, err = positionStmt.Exec( - sequentialID, - strings.ToUpper(semester), - pos.Tier, - officer.FullName, - pos.Title, - ) - if err != nil { - log.Printf("Error inserting position for officer %s: %v", officer.FullName, err) - continue - } - } - } - } + // Insert position + _, err = positionStmt.Exec( + sequentialID, + strings.ToUpper(semester), + pos.Tier, + officer.FullName, + pos.Title, + ) + if err != nil { + log.Printf("Error inserting position for officer %s: %v", officer.FullName, err) + continue + } + } + } + } - fmt.Println("Database population completed successfully!") - fmt.Printf("Processed %d officers\n", len(officers)) -} \ No newline at end of file + fmt.Println("Database population completed successfully!") + fmt.Printf("Processed %d officers\n", len(officers)) +} From ceace180b6513e0d51143fa5b47d5b06970b5916 Mon Sep 17 00:00:00 2001 From: Gaballa <123525159+GaballaGit@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:54:26 -0800 Subject: [PATCH 04/13] NO MORE INTERFACES (#112) * NO MORE INTERFACESnvim .gitignorenvim .gitignorenvim .gitignorenvim .gitignore ; * Add comment to schema.sql * update events cli --- .gitignore | 1 + internal/cli/events/post.go | 9 ++--- internal/cli/events/put.go | 13 +++---- internal/db/models/announcement.sql.go | 2 +- internal/db/models/board.sql.go | 52 +++++++++++++------------- internal/db/models/db.go | 2 +- internal/db/models/event.sql.go | 18 ++++----- internal/db/models/models.go | 20 +++++----- internal/db/models/person.sql.go | 2 +- internal/db/sql/schemas/schema.sql | 17 ++++++--- 10 files changed, 68 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 13efd77..63dd4b8 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ result tmp/ .env +.DS_Store diff --git a/internal/cli/events/post.go b/internal/cli/events/post.go index d73fae2..0abfea6 100644 --- a/internal/cli/events/post.go +++ b/internal/cli/events/post.go @@ -42,7 +42,7 @@ var PostEvent = &cobra.Command{ } if duration != "" { var err error - payload.EndAt, err = utils.TimeAfterDuration(payload.StartAt.(int64), duration) + payload.EndAt, err = utils.TimeAfterDuration(payload.StartAt, duration) if err != nil { fmt.Println(err) return @@ -68,7 +68,6 @@ var PostEvent = &cobra.Command{ } func init() { - // URL Flags PostEvent.Flags().String("urlhost", "127.0.0.1", "Custom host") PostEvent.Flags().String("port", "8080", "Custom port") @@ -81,10 +80,9 @@ func init() { PostEvent.Flags().StringP("host", "H", "", "Set host of new event") PostEvent.Flags().BoolP("isallday", "a", false, "Set if new event is all day") - } -func postEvent(urlhost string, port string, payload *models.CreateEventParams, changedFlag eventFlags) { +func postEvent(urlhost string, port string, payload *models.CreateEventParams, changedFlag eventFlags) { scanner := bufio.NewScanner(os.Stdin) // ----- Uuid ----- @@ -165,7 +163,7 @@ func postEvent(urlhost string, port string, payload *models.CreateEventParams, c } endTimeBuffer := scanner.Bytes() - endTime, err := utils.TimeAfterDuration(payload.StartAt.(int64), string(endTimeBuffer)) + endTime, err := utils.TimeAfterDuration(payload.StartAt, string(endTimeBuffer)) if err != nil { fmt.Println(err) continue @@ -283,5 +281,4 @@ func postEvent(urlhost string, port string, payload *models.CreateEventParams, c } fmt.Println("Response body:", string(body)) - } diff --git a/internal/cli/events/put.go b/internal/cli/events/put.go index 4d32282..3e5c999 100644 --- a/internal/cli/events/put.go +++ b/internal/cli/events/put.go @@ -22,7 +22,6 @@ var PutEvents = &cobra.Command{ Short: "Used to update an event", Run: func(cmd *cobra.Command, args []string) { - payload := models.CreateEventParams{} // CLI for url @@ -47,7 +46,7 @@ var PutEvents = &cobra.Command{ } if durationString != "" { var err error - payload.EndAt, err = utils.TimeAfterDuration(payload.StartAt.(int64), durationString) + payload.EndAt, err = utils.TimeAfterDuration(payload.StartAt, durationString) if err != nil { fmt.Println(err) return @@ -69,7 +68,6 @@ var PutEvents = &cobra.Command{ } func init() { - // URL Flags PutEvents.Flags().String("id", "", "Event to update") PutEvents.Flags().String("urlhost", "127.0.0.1", "Custom host") @@ -85,7 +83,6 @@ func init() { // This flag is neccessary PutEvents.MarkFlagRequired("id") - } func updateEvent(id string, host string, port string, payload *models.CreateEventParams, changedFlags eventFlags) { @@ -187,7 +184,7 @@ func updateEvent(id string, host string, port string, payload *models.CreateEven if changedFlags.startat { break } - changeTheEventStartAt, err := utils.ChangePrompt("start time (format: 01/02/06 03:04PM)", utils.FormatUnix(int64(oldpayload.StartAt.(float64))), scanner) + changeTheEventStartAt, err := utils.ChangePrompt("start time (format: 01/02/06 03:04PM)", utils.FormatUnix(oldpayload.StartAt), scanner) if err != nil { fmt.Println(err) continue @@ -210,7 +207,7 @@ func updateEvent(id string, host string, port string, payload *models.CreateEven if changedFlags.duration { break } - changeTheEventEndAt, err := utils.ChangePrompt("end time (format: 01/02/06 03:04 )", utils.FormatUnix(int64(oldpayload.EndAt.(float64))), scanner) + changeTheEventEndAt, err := utils.ChangePrompt("end time (format: 01/02/06 03:04 )", utils.FormatUnix(oldpayload.EndAt), scanner) if err != nil { fmt.Println(err) continue @@ -278,8 +275,8 @@ func updateEvent(id string, host string, port string, payload *models.CreateEven updatePayload := models.UpdateEventParams{ Uuid: payload.Uuid, Location: utils.StringtoNullString(payload.Location), - StartAt: utils.Int64toNullInt64(int64(payload.StartAt.(float64))), - EndAt: utils.Int64toNullInt64(int64(payload.EndAt.(float64))), + StartAt: utils.Int64toNullInt64(payload.StartAt), + EndAt: utils.Int64toNullInt64(payload.EndAt), IsAllDay: utils.BooltoNullBool(payload.IsAllDay), Host: utils.StringtoNullString(payload.Host), } diff --git a/internal/db/models/announcement.sql.go b/internal/db/models/announcement.sql.go index fcc84c3..cca4790 100644 --- a/internal/db/models/announcement.sql.go +++ b/internal/db/models/announcement.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.28.0 // source: announcement.sql package models diff --git a/internal/db/models/board.sql.go b/internal/db/models/board.sql.go index 1357157..7946aa6 100644 --- a/internal/db/models/board.sql.go +++ b/internal/db/models/board.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.28.0 // source: board.sql package models @@ -25,7 +25,7 @@ RETURNING uuid, full_name, picture, github, discord ` type CreateOfficerParams struct { - Uuid interface{} `json:"uuid"` + Uuid string `json:"uuid"` FullName string `json:"full_name"` Picture sql.NullString `json:"picture"` Github sql.NullString `json:"github"` @@ -64,9 +64,9 @@ RETURNING oid, semester, tier, full_name, title, team ` type CreatePositionParams struct { - Oid interface{} `json:"oid"` - Semester interface{} `json:"semester"` - Tier int64 `json:"tier"` + Oid string `json:"oid"` + Semester string `json:"semester"` + Tier int64 `json:"tier"` } func (q *Queries) CreatePosition(ctx context.Context, arg CreatePositionParams) (Position, error) { @@ -125,7 +125,7 @@ DELETE FROM officer WHERE uuid = ? ` -func (q *Queries) DeleteOfficer(ctx context.Context, uuid interface{}) error { +func (q *Queries) DeleteOfficer(ctx context.Context, uuid string) error { _, err := q.db.ExecContext(ctx, deleteOfficer, uuid) return err } @@ -139,9 +139,9 @@ WHERE ` type DeletePositionParams struct { - Oid interface{} `json:"oid"` - Semester interface{} `json:"semester"` - Tier int64 `json:"tier"` + Oid string `json:"oid"` + Semester string `json:"semester"` + Tier int64 `json:"tier"` } func (q *Queries) DeletePosition(ctx context.Context, arg DeletePositionParams) error { @@ -178,7 +178,7 @@ type GetOfficerRow struct { Discord sql.NullString `json:"discord"` } -func (q *Queries) GetOfficer(ctx context.Context, uuid interface{}) (GetOfficerRow, error) { +func (q *Queries) GetOfficer(ctx context.Context, uuid string) (GetOfficerRow, error) { row := q.db.QueryRowContext(ctx, getOfficer, uuid) var i GetOfficerRow err := row.Scan( @@ -191,13 +191,13 @@ func (q *Queries) GetOfficer(ctx context.Context, uuid interface{}) (GetOfficerR } const getOfficers = `-- name: GetOfficers :many -SELECT - uuid, +SELECT + uuid, full_name, picture, - github, - discord -FROM + github, + discord +FROM officer ` @@ -244,7 +244,7 @@ WHERE oid = ? ` -func (q *Queries) GetPosition(ctx context.Context, oid interface{}) (Position, error) { +func (q *Queries) GetPosition(ctx context.Context, oid string) (Position, error) { row := q.db.QueryRowContext(ctx, getPosition, oid) var i Position err := row.Scan( @@ -265,8 +265,8 @@ SELECT tier, full_name, title, - team -FROM + team +FROM position ` @@ -325,14 +325,14 @@ func (q *Queries) GetTier(ctx context.Context, tier int64) (Tier, error) { } const getTiers = `-- name: GetTiers :many -SELECT +SELECT tier, title, t_index, - team -FROM - tier -ORDER BY + team +FROM + tier +ORDER BY tier ` @@ -381,7 +381,7 @@ type UpdateOfficerParams struct { Picture sql.NullString `json:"picture"` Github sql.NullString `json:"github"` Discord sql.NullString `json:"discord"` - Uuid interface{} `json:"uuid"` + Uuid string `json:"uuid"` } // NOTE: Had to declare above table as :one, may need to change later to :many @@ -412,8 +412,8 @@ type UpdatePositionParams struct { FullName string `json:"full_name"` Title sql.NullString `json:"title"` Team sql.NullString `json:"team"` - Oid interface{} `json:"oid"` - Semester interface{} `json:"semester"` + Oid string `json:"oid"` + Semester string `json:"semester"` Tier int64 `json:"tier"` } diff --git a/internal/db/models/db.go b/internal/db/models/db.go index e8ec4c2..d4f3d12 100644 --- a/internal/db/models/db.go +++ b/internal/db/models/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.28.0 package models diff --git a/internal/db/models/event.sql.go b/internal/db/models/event.sql.go index 936599d..ab2cdea 100644 --- a/internal/db/models/event.sql.go +++ b/internal/db/models/event.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.28.0 // source: event.sql package models @@ -26,12 +26,12 @@ RETURNING uuid, location, start_at, end_at, is_all_day, host ` type CreateEventParams struct { - Uuid string `json:"uuid"` - Location string `json:"location"` - StartAt interface{} `json:"start_at"` - EndAt interface{} `json:"end_at"` - IsAllDay bool `json:"is_all_day"` - Host string `json:"host"` + Uuid string `json:"uuid"` + Location string `json:"location"` + StartAt int64 `json:"start_at"` + EndAt int64 `json:"end_at"` + IsAllDay bool `json:"is_all_day"` + Host string `json:"host"` } func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) error { @@ -140,8 +140,8 @@ WHERE type UpdateEventParams struct { Location sql.NullString `json:"location"` - StartAt interface{} `json:"start_at"` - EndAt interface{} `json:"end_at"` + StartAt sql.NullInt64 `json:"start_at"` + EndAt sql.NullInt64 `json:"end_at"` IsAllDay sql.NullBool `json:"is_all_day"` Host sql.NullString `json:"host"` Uuid string `json:"uuid"` diff --git a/internal/db/models/models.go b/internal/db/models/models.go index 3b2ae99..a4cc4c4 100644 --- a/internal/db/models/models.go +++ b/internal/db/models/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.28.0 package models @@ -17,16 +17,16 @@ type Announcement struct { } type Event struct { - Uuid string `json:"uuid"` - Location string `json:"location"` - StartAt interface{} `json:"start_at"` - EndAt interface{} `json:"end_at"` - IsAllDay bool `json:"is_all_day"` - Host string `json:"host"` + Uuid string `json:"uuid"` + Location string `json:"location"` + StartAt int64 `json:"start_at"` + EndAt int64 `json:"end_at"` + IsAllDay bool `json:"is_all_day"` + Host string `json:"host"` } type Officer struct { - Uuid interface{} `json:"uuid"` + Uuid string `json:"uuid"` FullName string `json:"full_name"` Picture sql.NullString `json:"picture"` Github sql.NullString `json:"github"` @@ -40,8 +40,8 @@ type Person struct { } type Position struct { - Oid interface{} `json:"oid"` - Semester interface{} `json:"semester"` + Oid string `json:"oid"` + Semester string `json:"semester"` Tier int64 `json:"tier"` FullName string `json:"full_name"` Title sql.NullString `json:"title"` diff --git a/internal/db/models/person.sql.go b/internal/db/models/person.sql.go index 7382269..48c119c 100644 --- a/internal/db/models/person.sql.go +++ b/internal/db/models/person.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.29.0 +// sqlc v1.28.0 // source: person.sql package models diff --git a/internal/db/sql/schemas/schema.sql b/internal/db/sql/schemas/schema.sql index 27c3fc0..ce50dca 100644 --- a/internal/db/sql/schemas/schema.sql +++ b/internal/db/sql/schemas/schema.sql @@ -1,11 +1,16 @@ -- Language: sqlite +-- Note: Using datatypes the sqlc does not know how to interpret (such as INT instead of INTEGER) +-- will result in sqlc generating interfaces, which is undesired. I recommend skimming the docs +-- if you are unsure about your sqlc: +-- https://docs.sqlc.dev/en/stable/reference/datatypes.html + -- Create the 'events' table which is a table of event resources. CREATE TABLE IF NOT EXISTS event ( uuid TEXT PRIMARY KEY, location TEXT NOT NULL, - start_at NUMBER NOT NULL, -- Start time in UTC milliseconds. - end_at NUMBER NOT NULL, + start_at INTEGER NOT NULL, -- Start time in UTC milliseconds. + end_at INTEGER NOT NULL, is_all_day BOOLEAN NOT NULL, host TEXT NOT NULL -- Accepts team ID or plain text. ); @@ -28,7 +33,7 @@ CREATE TABLE IF NOT EXISTS announcement ( ); CREATE TABLE IF NOT EXISTS officer ( - uuid CHAR(4) PRIMARY KEY, + uuid VARCHAR(4) PRIMARY KEY, full_name VARCHAR(30) NOT NULL, picture VARCHAR(37), github VARCHAR(64), @@ -43,9 +48,9 @@ CREATE TABLE IF NOT EXISTS tier ( ); CREATE TABLE IF NOT EXISTS position ( - oid CHAR(4) NOT NULL, - semester CHAR(3) NOT NULL, - tier INT NOT NULL, + oid VARCHAR(4) NOT NULL, + semester VARCHAR(3) NOT NULL, + tier INTEGER NOT NULL, full_name VARCHAR(30) NOT NULL, title VARCHAR(40), team VARCHAR(20), From d282d691bf80d3e0c3374b8381aa6f2eb209b5bb Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Fri, 21 Nov 2025 12:40:33 -0800 Subject: [PATCH 05/13] Docs: improve readme and contributor guide (#115) * update readme * update PR info in contributor guide * add `make help` target * update readme again * add port to .env.example * grammar --- .env.example | 1 + .github/CONTRIBUTING.md | 11 +++++-- Makefile | 28 +++++++++------- README.md | 73 ++++++++++++++++++++++++++++++++--------- 4 files changed, 82 insertions(+), 31 deletions(-) diff --git a/.env.example b/.env.example index 9cd8ec5..d1062b3 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ DATABASE_URL="file:dev.db?cache=shared&mode=rwc" +PORT="" diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b1cb6ec..287232f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -32,6 +32,11 @@ Don't be afraid to ask for help with anything on this project. DMs are open! - Occasionally rebasing will create merge conflicts out of thin air - in these cases it's fine to just merge. - Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue) if you are solving one. +- *Keep PRs small and focused*. If possible keep PRs under 300-400 lines of + changes. If you have a big change, maybe try breaking it into multiple smaller + PRs. + - Not a strict requirement, but it's well known that big PRs are harder to + review effectively and accurately. - Please request a review from a current project maintainer or ping us on discord when your pr is ready to merge. - We may respond to your review with some suggestions and/or changes that @@ -40,8 +45,8 @@ Don't be afraid to ask for help with anything on this project. DMs are open! - The Copilot PR review is a useful feature - it often catches easy to miss copy-paste errors, spelling/grammar mistakes, and other things. Be cautious though - it can make mistakes, and always validate the issues it finds. -- Only *squash merging and rebase merging* into main are allowed. Don't worry - about this too much, but if you don't know which to choose: +- *squash merging* into main is preferred. Don't worry about this too much, but + if you don't know which to choose: - Choose a squash merge if the individual commits in the PR don't matter very much and/or the change is relatively atomic. - - Choose a rebase merge if the individual commits in the PR do matter. + - Choose a rebase merge/normal merge if the individual commits in the PR do matter. diff --git a/Makefile b/Makefile index 4440182..050a65f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,9 @@ .DEFAULT_GOAL := build +.PHONY: help +help: ## Display this help screen + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + BIN_DIR := bin API_NAME := acmcsuf-api CLI_NAME := acmcsuf-cli @@ -7,9 +11,9 @@ CLI_NAME := acmcsuf-cli GENERATE_DEPS := $(wildcard internal/db/sql/schemas/*.sql) $(wildcard internal/db/sql/queries/*.sql) internal/db/sqlc.yaml $(wildcard internal/api/handlers/*.go) GENERATE_MARKER := .generate.marker -.PHONY:fmt run build vet check test check-sql fix-sql clean +.PHONY:fmt run build vet check test check-sql fix-sql clean release generate -fmt: +fmt: ## Format all go files @go fmt ./... VERSION := $(shell git describe --tags --always --dirty 2> /dev/null || echo "dev") @@ -18,39 +22,39 @@ $(GENERATE_MARKER): $(GENERATE_DEPS) go generate ./... @touch $@ -generate: fmt $(GENERATE_MARKER) +generate: fmt $(GENERATE_MARKER) ## Generate all necessary files -run: build +run: build ## Build and run the api ./$(BIN_DIR)/$(API_NAME) -build: generate +build: generate ## Build the api and cli binaries @mkdir -p $(BIN_DIR) go build -ldflags "-X main.Version=$(VERSION)" -o $(BIN_DIR)/$(API_NAME) ./cmd/$(API_NAME) go build -ldflags "-X main.Version=$(VERSION)" -o $(BIN_DIR)/$(CLI_NAME) ./cmd/$(CLI_NAME) -vet: +vet: ## Vet all go files go vet ./... -check: +check: ## Run static analysis on all go files staticcheck ./... -test: check +test: check ## Run all tests go test ./... -check-sql: +check-sql: ## Lint all sql files sqlfluff lint --dialect sqlite -fix-sql: +fix-sql: ## Fix all sql files sqlfluff fix --dialect sqlite -release: +release: ## Create a new release tag @echo "Current version: $(VERSION)" @read -p "Enter new version (e.g., v0.2.0): " version; \ git tag -a $$version -m "Release $$version"; \ git push origin $$version; \ echo "Tagged $$version." -clean: +clean: ## Clean up all generated files and binaries go clean rm -f $(GENERATE_MARKER) rm -rf $(BIN_DIR) result diff --git a/README.md b/README.md index 7e711ba..0e6ddd1 100644 --- a/README.md +++ b/README.md @@ -4,40 +4,81 @@ ACM at CSUF club API for managing events, announcements, forms, and other servic --- -This project requires that you have Go, sqlc, GNU Make, and optionally Air installed. We recommend using the -provided Nix development environment, see below. +## Setting Up +This project requires that you have Go, sqlc, GNU Make, and optionally Air installed. We highly +recommend using the provided Nix development environment instead of installing everything manually. -### Start API server -Air will recompile the project on the fly so you don't have to restart the server when you make changes. +1. [Install nix](https://determinate.systems/nix-installer/) and [direnv](https://direnv.net/docs/installation.html) if you don't already have them + +2. Run `direnv allow` at the project root +> If you don't have direnv, you can also use `nix develop` to enter the dev +> shell, but your environment variables won't get sourced automatically. + +## Developing + +### Compiling +Using `make` will compile both the API and CLI binaries, located at +`bin/acmcsuf-api` and `bin/acmcsuf-cli` respectively. If you installed `direnv`, +both of these binaries will be automatically added to your path. You can run +them directly like: ```sh -air +acmcsuf-api # Run API server +acmcsuf-cli # Start CLI client (start API server before using) ``` -To compile and run manually, you can use one of the following: + +### Start API server +Air will recompile the project on the fly so you don't have to restart the server when you make changes. ```sh -make # Compile program -./bin/api # Run program +air ``` OR ```sh -make run +make run # Compiles and runs directly (no hot-reloading) +``` + +#### Configuring the API +The API server is configurable via environment variables. See [`.env.example`](./.env.example) for +values you can override and configure in your `.env` file. + +### Using the CLI +The CLI is a simple command-line client for the API server. Make sure the API +server is running before using. + +Output of `acmcsuf-cli --help`: +``` +A CLI tool to help manage the API of the CSUF ACM website + +Usage: + acmcsuf-cli [command] + +Available Commands: + announcements Manage ACM CSUF's Announcements + completion Generate the autocompletion script for the specified shell + events A command to manage events. + help Help about any command + officers A command to manage officers. + +Flags: + -h, --help help for acmcsuf-cli + -v, --version version for acmcsuf-cli + +Use "acmcsuf-cli [command] --help" for more information about a command. ``` ### Other useful commands from the Makefile ```sh -make check # Run checks -make test # Run tests (None yet) -make sql-fix # Format and fix SQL files -make clean # Removes all build artifacts +make check # Run checks +make test # Run tests (None yet) +make fix-sql # Format and fix SQL files +make clean # Removes all build artifacts +make help # Display all targets ``` --- ## To use the Nix dev shell -1. [Install nix](https://determinate.systems/nix-installer/) and optionally [direnv](https://direnv.net/docs/installation.html) if you don't already have them - -2. Run `direnv allow` at the project root. If you don't want to use direnv, you can use `nix develop` to achieve the same thing, but you will need to run it every time you enter the project. Developed with 💚 by [**@acmcsufoss**](https://github.com/acmcsufoss) From 1470caae1fd3cf276b59447e9b704e39f7d1cbf4 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Fri, 21 Nov 2025 13:49:03 -0800 Subject: [PATCH 06/13] Create and use config object (#116) * add config * use config object * rename to ALLOWED_ORIGINS * formatting * set proxies correclty --- .env.example | 2 ++ internal/api/config/config.go | 19 +++++++++++++++++++ internal/api/server.go | 15 +++++---------- internal/db/db.go | 10 ++-------- utils/get_env.go | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 internal/api/config/config.go create mode 100644 utils/get_env.go diff --git a/.env.example b/.env.example index d1062b3..42c924e 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,4 @@ DATABASE_URL="file:dev.db?cache=shared&mode=rwc" PORT="" +TRUSTED_PROXIES="" +ALLOWED_ORIGINS="" diff --git a/internal/api/config/config.go b/internal/api/config/config.go new file mode 100644 index 0000000..c39d2df --- /dev/null +++ b/internal/api/config/config.go @@ -0,0 +1,19 @@ +package config + +import "github.com/acmcsufoss/api.acmcsuf.com/utils" + +type Config struct { + Port string + DatabaseURL string + TrustedProxies []string + AllowedOrigins []string +} + +func Load() *Config { + return &Config{ + Port: utils.GetEnv("PORT", "8080"), + DatabaseURL: utils.GetEnv("DATABASE_URL", "file:dev.db?cache=shared&mode=rwc"), + TrustedProxies: utils.GetEnvAsSlice("TRUSTED_PROXIES", []string{"127.0.0.1/32"}), + AllowedOrigins: utils.GetEnvAsSlice("ALLOWED_ORIGINS", []string{"*"}), + } +} diff --git a/internal/api/server.go b/internal/api/server.go index c54b894..6f64c9f 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -6,10 +6,10 @@ import ( "context" "fmt" "log" - "os" "github.com/gin-gonic/gin" + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/routes" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" "github.com/acmcsufoss/api.acmcsuf.com/internal/db" @@ -19,7 +19,8 @@ import ( // Run initializes the database, services, and router, then starts the server. // It waits for the context to be canceled to initiate a graceful shutdown. func Run(ctx context.Context) { - db, closer, err := db.New(ctx) + cfg := config.Load() + db, closer, err := db.New(ctx, cfg.DatabaseURL) if err != nil { log.Fatal(err) } @@ -32,18 +33,12 @@ func Run(ctx context.Context) { boardService := services.NewBoardService(queries, db) router := gin.Default() - router.SetTrustedProxies([]string{ - "127.0.0.1/32", - }) + router.SetTrustedProxies(cfg.TrustedProxies) routes.SetupRoot(router) routes.SetupV1(router, eventsService, announcementService, boardService) - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } go func() { - serverAddr := fmt.Sprintf("localhost:%s", port) + serverAddr := fmt.Sprintf("localhost:%s", cfg.Port) log.Printf("\x1b[32mServer started on http://%s\x1b[0m", serverAddr) if err := router.Run(serverAddr); err != nil { diff --git a/internal/db/db.go b/internal/db/db.go index 47811b6..9788c4b 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -5,20 +5,14 @@ package db import ( "context" "database/sql" - "errors" "fmt" "os" _ "modernc.org/sqlite" ) -func New(ctx context.Context) (*sql.DB, func(), error) { - uri := os.Getenv("DATABASE_URL") - if uri == "" { - return nil, nil, errors.New("DATABASE_URL must be set") - } - - db, err := sql.Open("sqlite", uri) +func New(ctx context.Context, url string) (*sql.DB, func(), error) { + db, err := sql.Open("sqlite", url) if err != nil { return nil, nil, fmt.Errorf("error opening SQLite database: %v", err) } diff --git a/utils/get_env.go b/utils/get_env.go new file mode 100644 index 0000000..30d60ad --- /dev/null +++ b/utils/get_env.go @@ -0,0 +1,33 @@ +package utils + +import ( + "os" + "strconv" + "strings" +) + +func GetEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + + return defaultValue +} + +func GetEnvAsSlice(key string, defaultValue []string) []string { + if value := os.Getenv(key); value != "" { + return strings.Split(value, ",") + } + + return defaultValue +} + +func GetBoolEnv(key string, defaultValue bool) bool { + if value := os.Getenv(key); value != "" { + if parsed, err := strconv.ParseBool(value); err == nil { + return parsed + } + } + + return defaultValue +} From f5f22f4f52e08fd93f7e7a51b4cfc024d6c6aede Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Fri, 21 Nov 2025 14:39:30 -0800 Subject: [PATCH 07/13] rm old heading (#120) --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 0e6ddd1..e99737a 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,4 @@ make help # Display all targets --- -## To use the Nix dev shell - - Developed with 💚 by [**@acmcsufoss**](https://github.com/acmcsufoss) From ead46b13d13fce1b49bbc7045a7d83491d52e3a2 Mon Sep 17 00:00:00 2001 From: Gaballa <123525159+GaballaGit@users.noreply.github.com> Date: Sat, 22 Nov 2025 10:06:02 -0800 Subject: [PATCH 08/13] Cli tools board officers (#113) --- cmd/acmcsuf-cli/main.go | 2 + docs/docs.go | 68 ++++-- docs/swagger.json | 68 ++++-- docs/swagger.yaml | 51 +++-- internal/cli/announcements/delete.go | 8 + internal/cli/announcements/get.go | 7 + internal/cli/announcements/post.go | 6 + internal/cli/announcements/put.go | 20 +- internal/cli/boards/officers/delete.go | 87 ++++++++ internal/cli/boards/officers/get.go | 85 ++++++++ internal/cli/boards/officers/officers.go | 25 +++ internal/cli/boards/officers/post.go | 217 +++++++++++++++++++ internal/cli/boards/officers/put.go | 265 +++++++++++++++++++++++ internal/cli/events/delete.go | 8 + internal/cli/events/get.go | 6 + internal/cli/events/post.go | 7 + internal/cli/events/put.go | 19 +- internal/db/models/announcement.sql.go | 2 +- internal/db/models/board.sql.go | 2 +- internal/db/models/db.go | 2 +- internal/db/models/event.sql.go | 2 +- internal/db/models/models.go | 2 +- internal/db/models/person.sql.go | 2 +- utils/cli_helper.go | 21 +- 24 files changed, 907 insertions(+), 75 deletions(-) create mode 100644 internal/cli/boards/officers/delete.go create mode 100644 internal/cli/boards/officers/get.go create mode 100644 internal/cli/boards/officers/officers.go create mode 100644 internal/cli/boards/officers/post.go create mode 100644 internal/cli/boards/officers/put.go diff --git a/cmd/acmcsuf-cli/main.go b/cmd/acmcsuf-cli/main.go index 07ebc74..3f1dbd8 100644 --- a/cmd/acmcsuf-cli/main.go +++ b/cmd/acmcsuf-cli/main.go @@ -5,6 +5,7 @@ import ( "os" "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/announcements" + "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/boards/officers" "github.com/acmcsufoss/api.acmcsuf.com/internal/cli/events" "github.com/spf13/cobra" @@ -31,6 +32,7 @@ func Execute() { func init() { rootCmd.AddCommand(events.CLIEvents) rootCmd.AddCommand(announcements.CLIAnnouncements) + rootCmd.AddCommand(officers.CLIOfficers) } func main() { diff --git a/docs/docs.go b/docs/docs.go index cb1b1ad..be77aef 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1337,7 +1337,9 @@ const docTemplate = `{ "models.CreateEventParams": { "type": "object", "properties": { - "end_at": {}, + "end_at": { + "type": "integer" + }, "host": { "type": "string" }, @@ -1347,7 +1349,9 @@ const docTemplate = `{ "location": { "type": "string" }, - "start_at": {}, + "start_at": { + "type": "integer" + }, "uuid": { "type": "string" } @@ -1368,14 +1372,20 @@ const docTemplate = `{ "picture": { "$ref": "#/definitions/sql.NullString" }, - "uuid": {} + "uuid": { + "type": "string" + } } }, "models.CreatePositionParams": { "type": "object", "properties": { - "oid": {}, - "semester": {}, + "oid": { + "type": "string" + }, + "semester": { + "type": "string" + }, "tier": { "type": "integer" } @@ -1401,8 +1411,12 @@ const docTemplate = `{ "models.DeletePositionParams": { "type": "object", "properties": { - "oid": {}, - "semester": {}, + "oid": { + "type": "string" + }, + "semester": { + "type": "string" + }, "tier": { "type": "integer" } @@ -1411,7 +1425,9 @@ const docTemplate = `{ "models.Event": { "type": "object", "properties": { - "end_at": {}, + "end_at": { + "type": "integer" + }, "host": { "type": "string" }, @@ -1421,7 +1437,9 @@ const docTemplate = `{ "location": { "type": "string" }, - "start_at": {}, + "start_at": { + "type": "integer" + }, "uuid": { "type": "string" } @@ -1442,7 +1460,9 @@ const docTemplate = `{ "picture": { "$ref": "#/definitions/sql.NullString" }, - "uuid": {} + "uuid": { + "type": "string" + } } }, "models.Position": { @@ -1451,8 +1471,12 @@ const docTemplate = `{ "full_name": { "type": "string" }, - "oid": {}, - "semester": {}, + "oid": { + "type": "string" + }, + "semester": { + "type": "string" + }, "team": { "$ref": "#/definitions/sql.NullString" }, @@ -1504,7 +1528,9 @@ const docTemplate = `{ "models.UpdateEventParams": { "type": "object", "properties": { - "end_at": {}, + "end_at": { + "$ref": "#/definitions/sql.NullInt64" + }, "host": { "$ref": "#/definitions/sql.NullString" }, @@ -1514,7 +1540,9 @@ const docTemplate = `{ "location": { "$ref": "#/definitions/sql.NullString" }, - "start_at": {}, + "start_at": { + "$ref": "#/definitions/sql.NullInt64" + }, "uuid": { "type": "string" } @@ -1535,7 +1563,9 @@ const docTemplate = `{ "picture": { "$ref": "#/definitions/sql.NullString" }, - "uuid": {} + "uuid": { + "type": "string" + } } }, "models.UpdatePositionParams": { @@ -1544,8 +1574,12 @@ const docTemplate = `{ "full_name": { "type": "string" }, - "oid": {}, - "semester": {}, + "oid": { + "type": "string" + }, + "semester": { + "type": "string" + }, "team": { "$ref": "#/definitions/sql.NullString" }, diff --git a/docs/swagger.json b/docs/swagger.json index e0b66a8..fbc28c5 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1326,7 +1326,9 @@ "models.CreateEventParams": { "type": "object", "properties": { - "end_at": {}, + "end_at": { + "type": "integer" + }, "host": { "type": "string" }, @@ -1336,7 +1338,9 @@ "location": { "type": "string" }, - "start_at": {}, + "start_at": { + "type": "integer" + }, "uuid": { "type": "string" } @@ -1357,14 +1361,20 @@ "picture": { "$ref": "#/definitions/sql.NullString" }, - "uuid": {} + "uuid": { + "type": "string" + } } }, "models.CreatePositionParams": { "type": "object", "properties": { - "oid": {}, - "semester": {}, + "oid": { + "type": "string" + }, + "semester": { + "type": "string" + }, "tier": { "type": "integer" } @@ -1390,8 +1400,12 @@ "models.DeletePositionParams": { "type": "object", "properties": { - "oid": {}, - "semester": {}, + "oid": { + "type": "string" + }, + "semester": { + "type": "string" + }, "tier": { "type": "integer" } @@ -1400,7 +1414,9 @@ "models.Event": { "type": "object", "properties": { - "end_at": {}, + "end_at": { + "type": "integer" + }, "host": { "type": "string" }, @@ -1410,7 +1426,9 @@ "location": { "type": "string" }, - "start_at": {}, + "start_at": { + "type": "integer" + }, "uuid": { "type": "string" } @@ -1431,7 +1449,9 @@ "picture": { "$ref": "#/definitions/sql.NullString" }, - "uuid": {} + "uuid": { + "type": "string" + } } }, "models.Position": { @@ -1440,8 +1460,12 @@ "full_name": { "type": "string" }, - "oid": {}, - "semester": {}, + "oid": { + "type": "string" + }, + "semester": { + "type": "string" + }, "team": { "$ref": "#/definitions/sql.NullString" }, @@ -1493,7 +1517,9 @@ "models.UpdateEventParams": { "type": "object", "properties": { - "end_at": {}, + "end_at": { + "$ref": "#/definitions/sql.NullInt64" + }, "host": { "$ref": "#/definitions/sql.NullString" }, @@ -1503,7 +1529,9 @@ "location": { "$ref": "#/definitions/sql.NullString" }, - "start_at": {}, + "start_at": { + "$ref": "#/definitions/sql.NullInt64" + }, "uuid": { "type": "string" } @@ -1524,7 +1552,9 @@ "picture": { "$ref": "#/definitions/sql.NullString" }, - "uuid": {} + "uuid": { + "type": "string" + } } }, "models.UpdatePositionParams": { @@ -1533,8 +1563,12 @@ "full_name": { "type": "string" }, - "oid": {}, - "semester": {}, + "oid": { + "type": "string" + }, + "semester": { + "type": "string" + }, "team": { "$ref": "#/definitions/sql.NullString" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 54db4cb..09353af 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -27,14 +27,16 @@ definitions: type: object models.CreateEventParams: properties: - end_at: {} + end_at: + type: integer host: type: string is_all_day: type: boolean location: type: string - start_at: {} + start_at: + type: integer uuid: type: string type: object @@ -48,12 +50,15 @@ definitions: $ref: '#/definitions/sql.NullString' picture: $ref: '#/definitions/sql.NullString' - uuid: {} + uuid: + type: string type: object models.CreatePositionParams: properties: - oid: {} - semester: {} + oid: + type: string + semester: + type: string tier: type: integer type: object @@ -70,21 +75,25 @@ definitions: type: object models.DeletePositionParams: properties: - oid: {} - semester: {} + oid: + type: string + semester: + type: string tier: type: integer type: object models.Event: properties: - end_at: {} + end_at: + type: integer host: type: string is_all_day: type: boolean location: type: string - start_at: {} + start_at: + type: integer uuid: type: string type: object @@ -98,14 +107,17 @@ definitions: $ref: '#/definitions/sql.NullString' picture: $ref: '#/definitions/sql.NullString' - uuid: {} + uuid: + type: string type: object models.Position: properties: full_name: type: string - oid: {} - semester: {} + oid: + type: string + semester: + type: string team: $ref: '#/definitions/sql.NullString' tier: @@ -139,14 +151,16 @@ definitions: type: object models.UpdateEventParams: properties: - end_at: {} + end_at: + $ref: '#/definitions/sql.NullInt64' host: $ref: '#/definitions/sql.NullString' is_all_day: $ref: '#/definitions/sql.NullBool' location: $ref: '#/definitions/sql.NullString' - start_at: {} + start_at: + $ref: '#/definitions/sql.NullInt64' uuid: type: string type: object @@ -160,14 +174,17 @@ definitions: $ref: '#/definitions/sql.NullString' picture: $ref: '#/definitions/sql.NullString' - uuid: {} + uuid: + type: string type: object models.UpdatePositionParams: properties: full_name: type: string - oid: {} - semester: {} + oid: + type: string + semester: + type: string team: $ref: '#/definitions/sql.NullString' tier: diff --git a/internal/cli/announcements/delete.go b/internal/cli/announcements/delete.go index 130c151..0ba24cc 100644 --- a/internal/cli/announcements/delete.go +++ b/internal/cli/announcements/delete.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" + "github.com/acmcsufoss/api.acmcsuf.com/utils" "github.com/spf13/cobra" ) @@ -33,6 +34,13 @@ func init() { } func deleteAnnouncement(host string, port string, id string) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + if id == "" { fmt.Println("ID is required to delete an announcement! Please use the --id flag") return diff --git a/internal/cli/announcements/get.go b/internal/cli/announcements/get.go index c8cceae..79d0ab7 100644 --- a/internal/cli/announcements/get.go +++ b/internal/cli/announcements/get.go @@ -35,6 +35,13 @@ func init() { } func getAnnouncement(host string, port string, uuid string) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + // ----- Constructing the url ----- host = fmt.Sprint(host, ":", port) path := "v1/announcements" diff --git a/internal/cli/announcements/post.go b/internal/cli/announcements/post.go index 0f56956..3a8c2c2 100644 --- a/internal/cli/announcements/post.go +++ b/internal/cli/announcements/post.go @@ -72,6 +72,12 @@ func init() { func postAnnouncement(host string, port string, payload *models.CreateAnnouncementParams, changedFlags announcementFlags) { + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + scanner := bufio.NewScanner(os.Stdin) // ----- Uuid ----- diff --git a/internal/cli/announcements/put.go b/internal/cli/announcements/put.go index 383df5c..d36f15b 100644 --- a/internal/cli/announcements/put.go +++ b/internal/cli/announcements/put.go @@ -59,7 +59,6 @@ var PutAnnouncements = &cobra.Command{ } func init() { - // Url flags PutAnnouncements.Flags().String("host", "127.0.0.1", "Set a custom host") PutAnnouncements.Flags().String("port", "8080", "Set a custom port") @@ -75,10 +74,16 @@ func init() { PutAnnouncements.Flags().StringP("messageid", "m", "", "Change this announcement's discord message id") PutAnnouncements.MarkFlagRequired("id") - } func putAnnouncements(host string, port string, id string, payload *models.UpdateAnnouncementParams, changedFlags announcementFlags) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + // ----- Check if Id was Given ----- if id == "" { fmt.Println("Announcement id required for put! Please use the --id flag") @@ -130,7 +135,7 @@ func putAnnouncements(host string, port string, id string, payload *models.Updat // Will probably remove uuid from all put cli soon for { if payload.Uuid == "" { - changeUuid, err := utils.ChangePrompt("uuid", oldPayload.Uuid, scanner) + changeUuid, err := utils.ChangePrompt("uuid", oldPayload.Uuid, scanner, "announcment") if err != nil { fmt.Println("error with changing uuid:", err) continue @@ -150,7 +155,7 @@ func putAnnouncements(host string, port string, id string, payload *models.Updat if changedFlags.visibility { break } - changeVisibility, err := utils.ChangePrompt("visibility", oldPayload.Visibility, scanner) + changeVisibility, err := utils.ChangePrompt("visibility", oldPayload.Visibility, scanner, "announcment") if err != nil { fmt.Println("error with changing visibility:", err) continue @@ -172,7 +177,7 @@ func putAnnouncements(host string, port string, id string, payload *models.Updat oldAnnounceAt := utils.FormatUnix(oldPayload.AnnounceAt) // Yah this might be a little sloppy in the terminal. forgive me. - changeAnnounceAt, err := utils.ChangePrompt("announce at (Note: format for new announcment is \"01/02/06 03:04PM\")", oldAnnounceAt, scanner) + changeAnnounceAt, err := utils.ChangePrompt("announce at (Note: format for new announcment is \"01/02/06 03:04PM\")", oldAnnounceAt, scanner, "announcment") if err != nil { fmt.Println("error with changing announce at:", err) continue @@ -197,7 +202,7 @@ func putAnnouncements(host string, port string, id string, payload *models.Updat break } - changeDiscordChannelID, err := utils.ChangePrompt("discord channel id", oldPayload.DiscordChannelID.String, scanner) + changeDiscordChannelID, err := utils.ChangePrompt("discord channel id", oldPayload.DiscordChannelID.String, scanner, "announcment") if err != nil { fmt.Println("error with changing :", err) continue @@ -216,7 +221,7 @@ func putAnnouncements(host string, port string, id string, payload *models.Updat if changedFlags.messageid { break } - changeDiscordMessageID, err := utils.ChangePrompt("discord message id", oldPayload.DiscordMessageID.String, scanner) + changeDiscordMessageID, err := utils.ChangePrompt("discord message id", oldPayload.DiscordMessageID.String, scanner, "announcment") if err != nil { fmt.Println("error with changing :", err) continue @@ -293,5 +298,4 @@ func putAnnouncements(host string, port string, id string, payload *models.Updat } fmt.Println(string(body)) - } diff --git a/internal/cli/boards/officers/delete.go b/internal/cli/boards/officers/delete.go new file mode 100644 index 0000000..f5b3407 --- /dev/null +++ b/internal/cli/boards/officers/delete.go @@ -0,0 +1,87 @@ +package officers + +import ( + "fmt" + "io" + "net/http" + "net/url" + + "github.com/acmcsufoss/api.acmcsuf.com/utils" + "github.com/spf13/cobra" +) + +var DeleteOfficers = &cobra.Command{ + Use: "delete --id ", + Short: "Delete an officer with their id", + + Run: func(cmd *cobra.Command, args []string) { + id, _ := cmd.Flags().GetString("id") + host, _ := cmd.Flags().GetString("host") + port, _ := cmd.Flags().GetString("port") + + deleteOfficer(id, host, port) + }, +} + +func init() { + DeleteOfficers.Flags().String("id", "", "Delete an officer by their id") + DeleteOfficers.Flags().String("host", "127.0.0.1", "Set a custom host") + DeleteOfficers.Flags().String("port", "8080", "Set a custom port") + + DeleteOfficers.MarkFlagRequired("id") +} + +func deleteOfficer(id, host, port string) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + + // req id + if id == "" { + fmt.Println("ID is required to delete!") + return + } + + // prepare url + host = fmt.Sprint(host, ":", port) + path := fmt.Sprint("v1/board/officers/", id) + + deleteURL := &url.URL{ + Scheme: "http", + Host: host, + Path: path, + } + + // send delete request + client := &http.Client{} + + request, err := http.NewRequest(http.MethodDelete, deleteURL.String(), nil) + if err != nil { + fmt.Println("Error making delete request:", err) + return + } + + response, err := client.Do(request) + if err != nil { + fmt.Println("Error with delete response:", err) + return + } + + if response == nil { + fmt.Println("no response received") + return + } + + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + if err != nil { + fmt.Println("Error reading delete response body:", err) + return + } + + fmt.Println(string(body)) +} diff --git a/internal/cli/boards/officers/get.go b/internal/cli/boards/officers/get.go new file mode 100644 index 0000000..c049e80 --- /dev/null +++ b/internal/cli/boards/officers/get.go @@ -0,0 +1,85 @@ +package officers + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/db/models" + "github.com/acmcsufoss/api.acmcsuf.com/utils" + "github.com/spf13/cobra" +) + +var GetOfficers = &cobra.Command{ + Use: "get [flags]", + Short: "Get Officers", + + Run: func(cmd *cobra.Command, args []string) { + id, _ := cmd.Flags().GetString("id") + host, _ := cmd.Flags().GetString("host") + port, _ := cmd.Flags().GetString("port") + + getOfficers(id, port, host) + }, +} + +func init() { + GetOfficers.Flags().String("id", "", "Get a specific officer") + GetOfficers.Flags().String("host", "127.0.0.1", "Custom host") + GetOfficers.Flags().String("port", "8080", "Custom port") +} + +func getOfficers(id, port, host string) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + + // prepare url + host = fmt.Sprint(host, ":", port) + path := fmt.Sprint("v1/board/officers/", id) + + getURL := &url.URL{ + Scheme: "http", + Host: host, + Path: path, + } + + // getting officer(s) + response, err := http.Get(getURL.String()) + if err != nil { + fmt.Println("error getting the request: ", err) + return + } + if response == nil { + fmt.Println("no response recieved") + return + } + + defer response.Body.Close() + + if id == "" { + var getPayload []models.GetOfficerRow + err = json.NewDecoder(response.Body).Decode(&getPayload) + if err != nil { + fmt.Println("Failed to read response body without id:", err) + return + } + + for i := range getPayload { + utils.PrintStruct(getPayload[i]) + } + } else { + var getPayload models.GetOfficerRow + err = json.NewDecoder(response.Body).Decode(&getPayload) + if err != nil { + fmt.Println("Failed to read response body with id:", err) + return + } + + utils.PrintStruct(getPayload) + } +} diff --git a/internal/cli/boards/officers/officers.go b/internal/cli/boards/officers/officers.go new file mode 100644 index 0000000..a732dac --- /dev/null +++ b/internal/cli/boards/officers/officers.go @@ -0,0 +1,25 @@ +package officers + +import ( + "github.com/spf13/cobra" +) + +type officerFlags struct { + uuid bool + fullname bool + picture bool + github bool + discord bool +} + +var CLIOfficers = &cobra.Command{ + Use: "officers HEADER", + Short: "A command to manage officers.", +} + +func init() { + CLIOfficers.AddCommand(GetOfficers) + CLIOfficers.AddCommand(DeleteOfficers) + CLIOfficers.AddCommand(PostOfficer) + CLIOfficers.AddCommand(PutOfficer) +} diff --git a/internal/cli/boards/officers/post.go b/internal/cli/boards/officers/post.go new file mode 100644 index 0000000..2ef545e --- /dev/null +++ b/internal/cli/boards/officers/post.go @@ -0,0 +1,217 @@ +package officers + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strings" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/db/models" + "github.com/acmcsufoss/api.acmcsuf.com/utils" + "github.com/spf13/cobra" +) + +var PostOfficer = &cobra.Command{ + Use: "post [flags]", + Short: "Post a new officer", + + Run: func(cmd *cobra.Command, args []string) { + var payload models.CreateOfficerParams + + host, _ := cmd.Flags().GetString("host") + port, _ := cmd.Flags().GetString("port") + + payload.Uuid, _ = cmd.Flags().GetString("uuid") + payload.FullName, _ = cmd.Flags().GetString("name") + pic, _ := cmd.Flags().GetString("picture") + payload.Picture = utils.StringtoNullString(pic) + git, _ := cmd.Flags().GetString("github") + payload.Github = utils.StringtoNullString(git) + disc, _ := cmd.Flags().GetString("discord") + payload.Discord = utils.StringtoNullString(disc) + + changedFlags := officerFlags{ + uuid: cmd.Flags().Lookup("uuid").Changed, + fullname: cmd.Flags().Lookup("name").Changed, + picture: cmd.Flags().Lookup("picture").Changed, + github: cmd.Flags().Lookup("github").Changed, + discord: cmd.Flags().Lookup("discord").Changed, + } + + postOfficer(&payload, &changedFlags, host, port) + }, +} + +func init() { + // Url flags + PostOfficer.Flags().String("host", "127.0.0.1", "Set a custom host") + PostOfficer.Flags().String("port", "8080", "Set a custom port") + + // Officer flags + PostOfficer.Flags().StringP("uuid", "u", "", "Set uuid of this officer") + PostOfficer.Flags().StringP("name", "n", "", "Set the full name of this officer") + PostOfficer.Flags().StringP("picture", "p", "", "Set the picture of this officer") + PostOfficer.Flags().StringP("github", "g", "", "Set the github of this officer") + PostOfficer.Flags().StringP("discord", "d", "", "Set the discord of this officer") +} + +func postOfficer(payload *models.CreateOfficerParams, cf *officerFlags, host, port string) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + + scanner := bufio.NewScanner(os.Stdin) + + // uuid + for { + if cf.uuid { + break + } + + fmt.Println("Please enter officer's uuid:") + scanner.Scan() + if err := scanner.Err(); err != nil { + fmt.Println(err) + continue + } + + payload.Uuid = string(scanner.Bytes()) + break + } + + // full name + for { + if cf.fullname { + break + } + + fmt.Println("Please enter the officer's full name:") + scanner.Scan() + if err := scanner.Err(); err != nil { + fmt.Println(err) + continue + } + + payload.FullName = string(scanner.Bytes()) + break + } + + // picture + for { + if cf.picture { + break + } + + fmt.Println("Please enter the picture link for officer:") + scanner.Scan() + if err := scanner.Err(); err != nil { + fmt.Println(err) + continue + } + + payload.Picture = utils.StringtoNullString(string(scanner.Bytes())) + break + } + + // github + for { + if cf.github { + break + } + + fmt.Println("Please enter the github link for officer:") + scanner.Scan() + if err := scanner.Err(); err != nil { + fmt.Println(err) + continue + } + + payload.Github = utils.StringtoNullString(string(scanner.Bytes())) + break + } + + // discord + for { + if cf.discord { + break + } + + fmt.Println("Please enter the discord link for officer:") + scanner.Scan() + if err := scanner.Err(); err != nil { + fmt.Println(err) + continue + } + + payload.Discord = utils.StringtoNullString(string(scanner.Bytes())) + break + + } + + // confirmation + for { + fmt.Println("Is your officer data correct? If not, type n or no.") + utils.PrintStruct(payload) + scanner.Scan() + if err := scanner.Err(); err != nil { + fmt.Println(err) + return + } + + confirmationBuffer := scanner.Bytes() + confirmationBool, err := utils.YesOrNo(confirmationBuffer, scanner) + if err != nil { + fmt.Println("error with reading confirmation:", err) + } + if !confirmationBool { + // Sorry :( + return + } else { + break + } + } + + // marshal to json, and prepare url + jsonPayload, err := json.Marshal(*payload) + if err != nil { + fmt.Println("error formating payload to json: ", err) + return + } + + host = fmt.Sprint(host, ":", port) + path := "v1/board/officers/" + + postURL := &url.URL{ + Scheme: "http", + Host: host, + Path: path, + } + + // post payload + response, err := http.Post(postURL.String(), "application/json", strings.NewReader(string(jsonPayload))) + if err != nil { + fmt.Println("error with post: ", err) + return + } + + if response == nil { + fmt.Println("error, no response recieved") + return + } + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + if err != nil { + fmt.Println("error reading body: ", err) + return + } + + fmt.Println(string(body)) +} diff --git a/internal/cli/boards/officers/put.go b/internal/cli/boards/officers/put.go new file mode 100644 index 0000000..3be2f4a --- /dev/null +++ b/internal/cli/boards/officers/put.go @@ -0,0 +1,265 @@ +package officers + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/db/models" + "github.com/acmcsufoss/api.acmcsuf.com/utils" + "github.com/spf13/cobra" +) + +var PutOfficer = &cobra.Command{ + Use: "put --id [flags]", + Short: "update an existing officer by id", + + Run: func(cmd *cobra.Command, args []string) { + payload := models.UpdateOfficerParams{} + + host, _ := cmd.Flags().GetString("host") + port, _ := cmd.Flags().GetString("port") + id, _ := cmd.Flags().GetString("id") + + fullname, _ := cmd.Flags().GetString("fullname") + picture, _ := cmd.Flags().GetString("picture") + github, _ := cmd.Flags().GetString("github") + discord, _ := cmd.Flags().GetString("discord") + uuid, _ := cmd.Flags().GetString("uuid") + + payload.FullName = fullname + payload.Picture = utils.StringtoNullString(picture) + payload.Github = utils.StringtoNullString(github) + payload.Discord = utils.StringtoNullString(discord) + payload.Uuid = uuid + + flags := officerFlags{ + fullname: cmd.Flags().Lookup("fullname").Changed, + picture: cmd.Flags().Lookup("picture").Changed, + github: cmd.Flags().Lookup("github").Changed, + discord: cmd.Flags().Lookup("discord").Changed, + uuid: cmd.Flags().Lookup("uuid").Changed, + } + + putOfficer(host, port, id, &payload, flags) + }, +} + +func init() { + PutOfficer.Flags().String("host", "127.0.0.1", "Set a custom host") + PutOfficer.Flags().String("port", "8080", "Set a custom port") + + PutOfficer.Flags().String("id", "", "Officer ID to update") + + PutOfficer.Flags().String("fullname", "", "Change full name") + PutOfficer.Flags().String("picture", "", "Change picture URL") + PutOfficer.Flags().String("github", "", "Change GitHub username") + PutOfficer.Flags().String("discord", "", "Change Discord tag") + PutOfficer.Flags().String("uuid", "", "Change uuid") + + PutOfficer.MarkFlagRequired("id") +} + +func putOfficer(host, port, id string, payload *models.UpdateOfficerParams, flags officerFlags) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + + if id == "" { + fmt.Println("Officer id required for put! Use --id") + return + } + + // construct url + hostPort := fmt.Sprint(host, ":", port) + path := "v1/board/officers/" + id + + u := &url.URL{ + Scheme: "http", + Host: hostPort, + Path: path, + } + + // getting old officer + resp, err := http.Get(u.String()) + if err != nil { + fmt.Printf("error retrieving %s: %s\n", id, err) + return + } + if resp == nil { + fmt.Println("no response received") + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("error reading response body:", err) + return + } + + var old models.CreateOfficerParams + if err := json.Unmarshal(body, &old); err != nil { + fmt.Println("error unmarshaling previous officer data:", err) + return + } + + scanner := bufio.NewScanner(os.Stdin) + + // full name + for { + if flags.fullname { + break + } + + change, err := utils.ChangePrompt("full name", old.FullName, scanner, "officer") + if err != nil { + fmt.Println(err) + continue + } + if change != nil { + payload.FullName = string(change) + } else { + payload.FullName = old.FullName + } + break + } + + // picture + for { + if flags.picture { + break + } + + change, err := utils.ChangePrompt("picture", old.Picture.String, scanner, "officer") + if err != nil { + fmt.Println(err) + continue + } + if change != nil { + payload.Picture = utils.StringtoNullString(string(change)) + } else { + payload.Picture = old.Picture + } + break + } + + // github + for { + if flags.github { + break + } + + change, err := utils.ChangePrompt("github", old.Github.String, scanner, "officer") + if err != nil { + fmt.Println(err) + continue + } + if change != nil { + payload.Github = utils.StringtoNullString(string(change)) + } else { + payload.Github = old.Github + } + break + } + + // discord + for { + if flags.discord { + break + } + + change, err := utils.ChangePrompt("discord", old.Discord.String, scanner, "officer") + if err != nil { + fmt.Println(err) + continue + } + if change != nil { + payload.Discord = utils.StringtoNullString(string(change)) + } else { + payload.Discord = old.Discord + } + break + } + + // uuid + for { + if flags.uuid { + break + } + + change, err := utils.ChangePrompt("uuid", old.Uuid, scanner, "officer") + if err != nil { + fmt.Println(err) + continue + } + if change != nil { + payload.Uuid = string(change) + } else { + payload.Uuid = old.Uuid + } + break + } + + // Confirm + for { + fmt.Println("Is the officer data correct? (y/n)") + utils.PrintStruct(payload) + scanner.Scan() + confirmation := scanner.Bytes() + + ok, err := utils.YesOrNo(confirmation, scanner) + if err != nil { + fmt.Println(err) + continue + } + if !ok { + return + } + break + } + + // marshal payload + jsonPayload, err := json.Marshal(*payload) + if err != nil { + fmt.Println("Error marshaling data:", err) + return + } + + // PUT + client := &http.Client{} + req, err := http.NewRequest(http.MethodPut, u.String(), bytes.NewBuffer(jsonPayload)) + if err != nil { + fmt.Println("Problem with PUT:", err) + return + } + + putResp, err := client.Do(req) + if err != nil { + fmt.Println("Error with response:", err) + return + } + if putResp == nil { + fmt.Println("no response received") + return + } + defer putResp.Body.Close() + + fmt.Println("PUT status:", putResp.Status) + + body, err = io.ReadAll(putResp.Body) + if err != nil { + fmt.Println("Error reading body:", err) + return + } + + fmt.Println(string(body)) +} diff --git a/internal/cli/events/delete.go b/internal/cli/events/delete.go index 72bdefa..531155f 100644 --- a/internal/cli/events/delete.go +++ b/internal/cli/events/delete.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" + "github.com/acmcsufoss/api.acmcsuf.com/utils" "github.com/spf13/cobra" ) @@ -34,6 +35,13 @@ func init() { } func deleteEvent(id string, host string, port string) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + // ----- Check if Event Id was Given ----- if id == "" { fmt.Println("Event ID is required to delete!") diff --git a/internal/cli/events/get.go b/internal/cli/events/get.go index c51db66..5ac110e 100644 --- a/internal/cli/events/get.go +++ b/internal/cli/events/get.go @@ -37,6 +37,12 @@ func init() { func getEvents(id string, port string, host string) { + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + // ----- Constructing url ----- // Combining Host and port host = fmt.Sprint(host, ":", port) diff --git a/internal/cli/events/post.go b/internal/cli/events/post.go index 0abfea6..e8c4b6b 100644 --- a/internal/cli/events/post.go +++ b/internal/cli/events/post.go @@ -83,6 +83,13 @@ func init() { } func postEvent(urlhost string, port string, payload *models.CreateEventParams, changedFlag eventFlags) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + scanner := bufio.NewScanner(os.Stdin) // ----- Uuid ----- diff --git a/internal/cli/events/put.go b/internal/cli/events/put.go index 3e5c999..c81a3f6 100644 --- a/internal/cli/events/put.go +++ b/internal/cli/events/put.go @@ -86,6 +86,13 @@ func init() { } func updateEvent(id string, host string, port string, payload *models.CreateEventParams, changedFlags eventFlags) { + + err := utils.CheckConnection() + if err != nil { + fmt.Println(err) + return + } + // ----- Check for Event Id ----- if id == "" { fmt.Println("Event ID is required!") @@ -145,7 +152,7 @@ func updateEvent(id string, host string, port string, payload *models.CreateEven // ----- uuid ----- for { if payload.Uuid == "" { - changeTheEventUuid, err := utils.ChangePrompt("uuid", oldpayload.Uuid, scanner) + changeTheEventUuid, err := utils.ChangePrompt("uuid", oldpayload.Uuid, scanner, "event") if err != nil { fmt.Println(err) // Custom errors in changePrompt() continue @@ -165,7 +172,7 @@ func updateEvent(id string, host string, port string, payload *models.CreateEven if changedFlags.location { break } - changeTheEventLocation, err := utils.ChangePrompt("location", oldpayload.Location, scanner) + changeTheEventLocation, err := utils.ChangePrompt("location", oldpayload.Location, scanner, "event") if err != nil { fmt.Println(err) continue @@ -184,7 +191,7 @@ func updateEvent(id string, host string, port string, payload *models.CreateEven if changedFlags.startat { break } - changeTheEventStartAt, err := utils.ChangePrompt("start time (format: 01/02/06 03:04PM)", utils.FormatUnix(oldpayload.StartAt), scanner) + changeTheEventStartAt, err := utils.ChangePrompt("start time (format: 01/02/06 03:04PM)", utils.FormatUnix(oldpayload.StartAt), scanner, "event") if err != nil { fmt.Println(err) continue @@ -207,7 +214,7 @@ func updateEvent(id string, host string, port string, payload *models.CreateEven if changedFlags.duration { break } - changeTheEventEndAt, err := utils.ChangePrompt("end time (format: 01/02/06 03:04 )", utils.FormatUnix(oldpayload.EndAt), scanner) + changeTheEventEndAt, err := utils.ChangePrompt("end time (format: 01/02/06 03:04 )", utils.FormatUnix(oldpayload.EndAt), scanner, "event") if err != nil { fmt.Println(err) continue @@ -232,7 +239,7 @@ func updateEvent(id string, host string, port string, payload *models.CreateEven break } - changeTheEventAllDay, err := utils.ChangePrompt("all day status", strconv.FormatBool(oldpayload.IsAllDay), scanner) + changeTheEventAllDay, err := utils.ChangePrompt("all day status", strconv.FormatBool(oldpayload.IsAllDay), scanner, "event") if err != nil { fmt.Println(err) continue @@ -256,7 +263,7 @@ func updateEvent(id string, host string, port string, payload *models.CreateEven if changedFlags.host { break } - changeTheEventHost, err := utils.ChangePrompt("host", oldpayload.Host, scanner) + changeTheEventHost, err := utils.ChangePrompt("host", oldpayload.Host, scanner, "event") if err != nil { fmt.Println(err) continue diff --git a/internal/db/models/announcement.sql.go b/internal/db/models/announcement.sql.go index cca4790..fcc84c3 100644 --- a/internal/db/models/announcement.sql.go +++ b/internal/db/models/announcement.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: announcement.sql package models diff --git a/internal/db/models/board.sql.go b/internal/db/models/board.sql.go index 7946aa6..5c7db61 100644 --- a/internal/db/models/board.sql.go +++ b/internal/db/models/board.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: board.sql package models diff --git a/internal/db/models/db.go b/internal/db/models/db.go index d4f3d12..e8ec4c2 100644 --- a/internal/db/models/db.go +++ b/internal/db/models/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 package models diff --git a/internal/db/models/event.sql.go b/internal/db/models/event.sql.go index ab2cdea..b9d7f6f 100644 --- a/internal/db/models/event.sql.go +++ b/internal/db/models/event.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: event.sql package models diff --git a/internal/db/models/models.go b/internal/db/models/models.go index a4cc4c4..64a3fe1 100644 --- a/internal/db/models/models.go +++ b/internal/db/models/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 package models diff --git a/internal/db/models/person.sql.go b/internal/db/models/person.sql.go index 48c119c..7382269 100644 --- a/internal/db/models/person.sql.go +++ b/internal/db/models/person.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.28.0 +// sqlc v1.29.0 // source: person.sql package models diff --git a/utils/cli_helper.go b/utils/cli_helper.go index 91b0322..d103af6 100644 --- a/utils/cli_helper.go +++ b/utils/cli_helper.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "math" + "net/http" "reflect" "regexp" "strconv" @@ -17,8 +18,8 @@ import ( // --------------------------- Printing to terminal --------------------------- // Returns a byte slice, if nil, no changes shall be made. Else, if a byte slice were to return, change the payload value -func ChangePrompt(dataToBeChanged string, currentData string, scanner *bufio.Scanner) ([]byte, error) { - fmt.Printf("Would you like to change this event's \x1b[1m%s\x1b[0m?[y/n]\nCurrent event's %s: \x1b[93m%s\x1b[0m\n", dataToBeChanged, dataToBeChanged, currentData) +func ChangePrompt(dataToBeChanged string, currentData string, scanner *bufio.Scanner, entity string) ([]byte, error) { + fmt.Printf("Would you like to change this %s's \x1b[1m%s\x1b[0m?[y/n]\nCurrent %s's %s: \x1b[93m%s\x1b[0m\n", entity, dataToBeChanged, entity, dataToBeChanged, currentData) scanner.Scan() if err := scanner.Err(); err != nil { return nil, fmt.Errorf("error reading input: %s", err) @@ -30,7 +31,7 @@ func ChangePrompt(dataToBeChanged string, currentData string, scanner *bufio.Sca return nil, err } if changeData { - fmt.Printf("Please enter a new \x1b[1m%s\x1b[0m for the event:\n", dataToBeChanged) + fmt.Printf("Please enter a new \x1b[1m%s\x1b[0m for the %s:\n", dataToBeChanged, entity) scanner.Scan() if err := scanner.Err(); err != nil { return nil, fmt.Errorf("error reading new %s: %s", dataToBeChanged, err) @@ -130,6 +131,18 @@ func PrintStruct(s any) { fmt.Println("----------------------------------------------------------------") } +func CheckConnection() error { + + _, err := http.Get("http://localhost:8080/health") + if err != nil { + return fmt.Errorf("\x1b[1;37;41mUNABLE TO CONNECT\x1b[0m | %s\n\t↳ %v", + "Did you forget to start the server?", + err) + } + return nil + +} + // --------------------------- Getting values from input --------------------------- // YesOrNo checks the user input for a yes or no response. @@ -165,7 +178,7 @@ func TimeAfterDuration(startTime int64, duration string) (int64, error) { durHour := parsedDuration[1] durMin := parsedDuration[2] - //fmt.Println("Parsed times:", durHour, durMin, durSec) + // fmt.Println("Parsed times:", durHour, durMin, durSec) intDurHour, err := strconv.Atoi(durHour) if err != nil { return -1, fmt.Errorf("error converting hour to int: %s", err) From f10a0ce96351dbd63d4292df0fa55d14047aa6c9 Mon Sep 17 00:00:00 2001 From: Siddharth Vasu Date: Sun, 23 Nov 2025 15:54:50 -0800 Subject: [PATCH 09/13] Moved files to db --- init_db.go => internal/db/init_db.go | 5 +-- officers.json => internal/db/officers.json | 0 internal/db/tiers.json | 39 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) rename init_db.go => internal/db/init_db.go (99%) rename officers.json => internal/db/officers.json (100%) create mode 100644 internal/db/tiers.json diff --git a/init_db.go b/internal/db/init_db.go similarity index 99% rename from init_db.go rename to internal/db/init_db.go index e322622..d14cde7 100644 --- a/init_db.go +++ b/internal/db/init_db.go @@ -1,13 +1,14 @@ -package main +package db import ( "database/sql" "encoding/json" "fmt" - _ "github.com/mattn/go-sqlite3" "log" "os" "strings" + + _ "github.com/mattn/go-sqlite3" ) type Officer struct { diff --git a/officers.json b/internal/db/officers.json similarity index 100% rename from officers.json rename to internal/db/officers.json diff --git a/internal/db/tiers.json b/internal/db/tiers.json new file mode 100644 index 0000000..56fda75 --- /dev/null +++ b/internal/db/tiers.json @@ -0,0 +1,39 @@ +{ + "President": { "id": 0, "index": 100 }, + "Vice President": { "id": 1, "index": 150 }, + "Webmaster": { "id": 2, "index": 200 }, + "Web Officer": { "id": 3, "index": 250 }, + "Treasurer": { "id": 4, "index": 300 }, + "Secretary": { "id": 5, "index": 350 }, + "Event Coordinator": { "id": 6, "index": 400 }, + "Data Analyst": { "id": 7, "index": 450 }, + "Marketing Team Lead": { "id": 8, "index": 500 }, + "Marketing Officer": { "id": 9, "index": 550 }, + "Historian": { "id": 10, "index": 600 }, + "Algo Team Lead": { "id": 11, "index": 650 }, + "Algo Officer": { "id": 12, "index": 700 }, + "Design Team Lead": { "id": 13, "index": 750 }, + "Design Project Manager": { "id": 14, "index": 800 }, + "Design Operations Manager": { "id": 15, "index": 850 }, + "Design Officer": { "id": 16, "index": 900 }, + "Dev Team Lead": { "id": 17, "index": 950 }, + "Dev Project Manager": { "id": 18, "index": 1000 }, + "Server Team Member": { "id": 37, "index": 1750 }, + "Dev Officer": { "id": 19, "index": 1050 }, + "AI Team Lead": { "id": 20, "index": 1100 }, + "AI Officer": { "id": 21, "index": 1150 }, + "Game Dev Team Lead": { "id": 22, "index": 1200 }, + "Game Dev Officer": { "id": 23, "index": 1250 }, + "Special Events Team Lead": { "id": 24, "index": 1300 }, + "Special Events Officer": { "id": 25, "index": 1350 }, + "Node Buds Team Lead": { "id": 34, "index": 1360 }, + "Node Buds Co-Team Lead": { "id": 36, "index": 1380 }, + "Node Buds Officer": { "id": 26, "index": 1400 }, + "Workshop Manager": { "id": 28, "index": 1450 }, + "Intern Program Manager": { "id": 29, "index": 1500 }, + "Community Manager": { "id": 30, "index": 1550 }, + "ICC Representative": { "id": 31, "index": 1600 }, + "Open Source Team Lead": { "id": 32, "index": 1650 }, + "Open Source Officer": { "id": 33, "index": 1700 }, + "ICPC Lead": { "id": 27, "index": 1750 } +} \ No newline at end of file From 2507d9968fffc8017ff1c2dec96c942457e75703 Mon Sep 17 00:00:00 2001 From: Siddharth Vasu Date: Sun, 23 Nov 2025 16:37:06 -0800 Subject: [PATCH 10/13] Imported board model and labeled direct SQL calls --- internal/db/init_db.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/internal/db/init_db.go b/internal/db/init_db.go index d14cde7..e7f6f9d 100644 --- a/internal/db/init_db.go +++ b/internal/db/init_db.go @@ -8,9 +8,15 @@ import ( "os" "strings" + "github.com/acmcsufoss/api.acmcsuf.com/internal/db/models" _ "github.com/mattn/go-sqlite3" ) +type BoardService struct { + q *models.Queries + db models.DBTX +} + type Officer struct { FullName string `json:"fullName"` Picture string `json:"picture"` @@ -40,6 +46,7 @@ func main() { defer db.Close() // Create tables if they don't exist + // DIRECT SQL CALL (will remove later) _, err = db.Exec(` CREATE TABLE IF NOT EXISTS officer ( uuid CHAR(4) PRIMARY KEY, @@ -73,18 +80,21 @@ func main() { } // Prepare statements + // DIRECT SQL CALL (will remove later) officerStmt, err := db.Prepare("INSERT OR IGNORE INTO officer (uuid, full_name, picture, discord) VALUES (?, ?, ?, ?)") if err != nil { log.Fatal("Error preparing officer statement:", err) } defer officerStmt.Close() + // DIRECT SQL CALL (will remove later) tierStmt, err := db.Prepare("INSERT OR IGNORE INTO tier (tier, title) VALUES (?, ?)") if err != nil { log.Fatal("Error preparing tier statement:", err) } defer tierStmt.Close() + // DIRECT SQL CALL (will remove later) positionStmt, err := db.Prepare("INSERT OR IGNORE INTO position (oid, semester, tier, full_name, title) VALUES (?, ?, ?, ?, ?)") if err != nil { log.Fatal("Error preparing position statement:", err) @@ -97,6 +107,7 @@ func main() { sequentialID := fmt.Sprintf("%04d", i+1) // Insert officer + // DIRECT SQL CALL (will remove later) _, err = officerStmt.Exec(sequentialID, officer.FullName, officer.Picture, officer.Discord) if err != nil { log.Printf("Error inserting officer %s: %v", officer.FullName, err) @@ -107,6 +118,7 @@ func main() { for semester, positions := range officer.Positions { for _, pos := range positions { // Insert tier if it doesn't exist yet + // DIRECT SQL CALL (will remove later) _, err = tierStmt.Exec(pos.Tier, pos.Title) if err != nil { log.Printf("Error inserting tier %d for officer %s: %v", pos.Tier, officer.FullName, err) @@ -114,6 +126,7 @@ func main() { } // Insert position + // DIRECT SQL CALL (will remove later) _, err = positionStmt.Exec( sequentialID, strings.ToUpper(semester), From ddf7710e4543f612af0852bb403e125e7494c8b9 Mon Sep 17 00:00:00 2001 From: Siddharth Vasu Date: Sun, 30 Nov 2025 16:08:48 -0800 Subject: [PATCH 11/13] Refactored tier population --- internal/db/init_db.go | 208 ++++++++++++++++++++++++----------------- 1 file changed, 121 insertions(+), 87 deletions(-) diff --git a/internal/db/init_db.go b/internal/db/init_db.go index e7f6f9d..f05b849 100644 --- a/internal/db/init_db.go +++ b/internal/db/init_db.go @@ -1,12 +1,11 @@ package db import ( - "database/sql" + "context" "encoding/json" "fmt" "log" "os" - "strings" "github.com/acmcsufoss/api.acmcsuf.com/internal/db/models" _ "github.com/mattn/go-sqlite3" @@ -28,119 +27,154 @@ type Officer struct { } func main() { - data, err := os.ReadFile("officers.json") + var s *BoardService + var ctx context.Context + + // Populating tiers + data, err := os.ReadFile("tiers.json") if err != nil { log.Fatal("Error reading JSON file:", err) } - var officers []Officer - err = json.Unmarshal(data, &officers) + var tiers []models.CreateTierParams + err = json.Unmarshal(data, &tiers) if err != nil { log.Fatal("Error unmarshaling JSON:", err) } - db, err := sql.Open("sqlite3", "./dev.db") - if err != nil { - log.Fatal("Error opening database:", err) - } - defer db.Close() - - // Create tables if they don't exist - // DIRECT SQL CALL (will remove later) - _, err = db.Exec(` - CREATE TABLE IF NOT EXISTS officer ( - uuid CHAR(4) PRIMARY KEY, - full_name VARCHAR(30) NOT NULL, - picture VARCHAR(37), - github VARCHAR(64), - discord VARCHAR(32) - ); - - CREATE TABLE IF NOT EXISTS tier ( - tier INT PRIMARY KEY, - title VARCHAR(40), - t_index INT, - team VARCHAR(20) - ); - - CREATE TABLE IF NOT EXISTS position ( - oid CHAR(4) NOT NULL, - semester CHAR(3) NOT NULL, - tier INT NOT NULL, - full_name VARCHAR(30) NOT NULL, - title VARCHAR(40), - team VARCHAR(20), - PRIMARY KEY (oid, semester, tier), - CONSTRAINT fk_officers FOREIGN KEY (oid) REFERENCES officer (uuid), - CONSTRAINT fk_tiers FOREIGN KEY (tier) REFERENCES tier(tier) - ); - `) - if err != nil { - log.Fatal("Error creating tables:", err) + for i := range tiers { + s.q.CreateTier(ctx, tiers[i]) } - // Prepare statements - // DIRECT SQL CALL (will remove later) - officerStmt, err := db.Prepare("INSERT OR IGNORE INTO officer (uuid, full_name, picture, discord) VALUES (?, ?, ?, ?)") + // Populating officers and positions + data, err = os.ReadFile("officers.json") if err != nil { - log.Fatal("Error preparing officer statement:", err) + log.Fatal("Error reading JSON file:", err) } - defer officerStmt.Close() - // DIRECT SQL CALL (will remove later) - tierStmt, err := db.Prepare("INSERT OR IGNORE INTO tier (tier, title) VALUES (?, ?)") + var officers []Officer + err = json.Unmarshal(data, &officers) if err != nil { - log.Fatal("Error preparing tier statement:", err) + log.Fatal("Error unmarshaling JSON:", err) } - defer tierStmt.Close() - // DIRECT SQL CALL (will remove later) - positionStmt, err := db.Prepare("INSERT OR IGNORE INTO position (oid, semester, tier, full_name, title) VALUES (?, ?, ?, ?, ?)") - if err != nil { - log.Fatal("Error preparing position statement:", err) - } - defer positionStmt.Close() + /* + var officers []Officer + err = json.Unmarshal(data, &officers) + if err != nil { + log.Fatal("Error unmarshaling JSON:", err) + } + + data, err = os.ReadFile("officers.json") + if err != nil { + log.Fatal("Error reading JSON file:", err) + } - // Insert officers - for i, officer := range officers { - // Generate sequential 4-digit ID (0001, 0002, etc.) - sequentialID := fmt.Sprintf("%04d", i+1) - // Insert officer - // DIRECT SQL CALL (will remove later) - _, err = officerStmt.Exec(sequentialID, officer.FullName, officer.Picture, officer.Discord) + db, err = sql.Open("sqlite3", "./dev.db") if err != nil { - log.Printf("Error inserting officer %s: %v", officer.FullName, err) - continue + log.Fatal("Error opening database:", err) } + defer db.Close() - // Insert all positions the officer holds - for semester, positions := range officer.Positions { - for _, pos := range positions { - // Insert tier if it doesn't exist yet + + // Create tables if they don't exist // DIRECT SQL CALL (will remove later) - _, err = tierStmt.Exec(pos.Tier, pos.Title) + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS officer ( + uuid CHAR(4) PRIMARY KEY, + full_name VARCHAR(30) NOT NULL, + picture VARCHAR(37), + github VARCHAR(64), + discord VARCHAR(32) + ); + + CREATE TABLE IF NOT EXISTS tier ( + tier INT PRIMARY KEY, + title VARCHAR(40), + t_index INT, + team VARCHAR(20) + ); + + CREATE TABLE IF NOT EXISTS position ( + oid CHAR(4) NOT NULL, + semester CHAR(3) NOT NULL, + tier INT NOT NULL, + full_name VARCHAR(30) NOT NULL, + title VARCHAR(40), + team VARCHAR(20), + PRIMARY KEY (oid, semester, tier), + CONSTRAINT fk_officers FOREIGN KEY (oid) REFERENCES officer (uuid), + CONSTRAINT fk_tiers FOREIGN KEY (tier) REFERENCES tier(tier) + ); + `) if err != nil { - log.Printf("Error inserting tier %d for officer %s: %v", pos.Tier, officer.FullName, err) - continue + log.Fatal("Error creating tables:", err) } - // Insert position + // Prepare statements // DIRECT SQL CALL (will remove later) - _, err = positionStmt.Exec( - sequentialID, - strings.ToUpper(semester), - pos.Tier, - officer.FullName, - pos.Title, - ) + officerStmt, err := db.Prepare("INSERT OR IGNORE INTO officer (uuid, full_name, picture, discord) VALUES (?, ?, ?, ?)") if err != nil { - log.Printf("Error inserting position for officer %s: %v", officer.FullName, err) - continue + log.Fatal("Error preparing officer statement:", err) } - } - } - } + defer officerStmt.Close() + + // DIRECT SQL CALL (will remove later) + tierStmt, err := db.Prepare("INSERT OR IGNORE INTO tier (tier, title) VALUES (?, ?)") + if err != nil { + log.Fatal("Error preparing tier statement:", err) + } + defer tierStmt.Close() + + // DIRECT SQL CALL (will remove later) + positionStmt, err := db.Prepare("INSERT OR IGNORE INTO position (oid, semester, tier, full_name, title) VALUES (?, ?, ?, ?, ?)") + if err != nil { + log.Fatal("Error preparing position statement:", err) + } + defer positionStmt.Close() + + // Insert officers + for i, officer := range officers { + // Generate sequential 4-digit ID (0001, 0002, etc.) + sequentialID := fmt.Sprintf("%04d", i+1) + + // Insert officer + // DIRECT SQL CALL (will remove later) + _, err = officerStmt.Exec(sequentialID, officer.FullName, officer.Picture, officer.Discord) + if err != nil { + log.Printf("Error inserting officer %s: %v", officer.FullName, err) + continue + } + + // Insert all positions the officer holds + for semester, positions := range officer.Positions { + for _, pos := range positions { + // Insert tier if it doesn't exist yet + // DIRECT SQL CALL (will remove later) + _, err = tierStmt.Exec(pos.Tier, pos.Title) + if err != nil { + log.Printf("Error inserting tier %d for officer %s: %v", pos.Tier, officer.FullName, err) + continue + } + + // Insert position + // DIRECT SQL CALL (will remove later) + _, err = positionStmt.Exec( + sequentialID, + strings.ToUpper(semester), + pos.Tier, + officer.FullName, + pos.Title, + ) + if err != nil { + log.Printf("Error inserting position for officer %s: %v", officer.FullName, err) + continue + } + } + } + } + */ fmt.Println("Database population completed successfully!") fmt.Printf("Processed %d officers\n", len(officers)) From 1711012566c8a13070fb4bbbfbef993533f4468d Mon Sep 17 00:00:00 2001 From: Siddharth Vasu Date: Sun, 7 Dec 2025 16:40:04 -0800 Subject: [PATCH 12/13] Refactored officer population (unfinished) --- internal/db/init_db.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/internal/db/init_db.go b/internal/db/init_db.go index f05b849..76fc024 100644 --- a/internal/db/init_db.go +++ b/internal/db/init_db.go @@ -16,7 +16,8 @@ type BoardService struct { db models.DBTX } -type Officer struct { +// Needed because the officers.json file stores officer and position data together +type OfficerPositions struct { FullName string `json:"fullName"` Picture string `json:"picture"` Positions map[string][]struct { @@ -24,6 +25,7 @@ type Officer struct { Tier int `json:"tier"` } `json:"positions"` Discord string `json:"discord,omitempty"` + Github string `json:"github,omitempty"` } func main() { @@ -52,12 +54,31 @@ func main() { log.Fatal("Error reading JSON file:", err) } - var officers []Officer - err = json.Unmarshal(data, &officers) + var officerPositions []OfficerPositions + err = json.Unmarshal(data, &officerPositions) if err != nil { log.Fatal("Error unmarshaling JSON:", err) } + var officer models.CreateOfficerParams + var position []models.CreatePositionParams + + // Splits up officerPositions into officer data and position data, then populates + for i := range officerPositions { + officer.Uuid = fmt.Sprintf("%04d", i+1) + officer.FullName = officerPositions[i].FullName + officer.Picture = officerPositions[i].Picture + officer.Discord = officerPositions[i].Discord + officer.Github = "" + + s.q.CreateOfficer(ctx, officer) + + for n := range len(officerPositions[i].Positions) { + position[n].Title = officerPositions[i].Positions.Title + position[n].Tier = officerPositions[i].Positions.Tier + } + } + /* var officers []Officer err = json.Unmarshal(data, &officers) @@ -177,5 +198,5 @@ func main() { */ fmt.Println("Database population completed successfully!") - fmt.Printf("Processed %d officers\n", len(officers)) + fmt.Printf("Processed %d officers\n", len(officerPositions)) } From a9aad3c0041ca8d66c3b785aa86cf66240905e19 Mon Sep 17 00:00:00 2001 From: Siddharth Vasu Date: Sun, 28 Dec 2025 20:27:56 -0800 Subject: [PATCH 13/13] Fixed officer population errors --- internal/db/init_db.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/db/init_db.go b/internal/db/init_db.go index 76fc024..df11735 100644 --- a/internal/db/init_db.go +++ b/internal/db/init_db.go @@ -2,13 +2,13 @@ package db import ( "context" + "database/sql" "encoding/json" "fmt" "log" "os" "github.com/acmcsufoss/api.acmcsuf.com/internal/db/models" - _ "github.com/mattn/go-sqlite3" ) type BoardService struct { @@ -18,14 +18,14 @@ type BoardService struct { // Needed because the officers.json file stores officer and position data together type OfficerPositions struct { - FullName string `json:"fullName"` - Picture string `json:"picture"` + FullName string `json:"fullName"` + Picture sql.NullString `json:"picture"` Positions map[string][]struct { Title string `json:"title"` Tier int `json:"tier"` } `json:"positions"` - Discord string `json:"discord,omitempty"` - Github string `json:"github,omitempty"` + Discord sql.NullString `json:"discord,omitempty"` + Github sql.NullString `json:"github,omitempty"` } func main() { @@ -69,13 +69,15 @@ func main() { officer.FullName = officerPositions[i].FullName officer.Picture = officerPositions[i].Picture officer.Discord = officerPositions[i].Discord - officer.Github = "" + officer.Github = officerPositions[i].Github s.q.CreateOfficer(ctx, officer) for n := range len(officerPositions[i].Positions) { - position[n].Title = officerPositions[i].Positions.Title + position[n].Oid = officer.Uuid + position[n].Semester = officerPositions[i].Positions.Semester position[n].Tier = officerPositions[i].Positions.Tier + s.q.CreatePosition(ctx, position[n]) } }