Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions goapp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work
go.work.sum

# Environment variables file
.env

# Build directory
/build/
/dist/

# IDE settings folder
.idea/
.vscode/
47 changes: 47 additions & 0 deletions goapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Go Stock Data Fetcher

This application fetches and displays recent stock data for a predefined symbol (currently IBM) using the Alpha Vantage API.

## Prerequisites

- Go (version 1.18 or later recommended)
- An Alpha Vantage API Key

## Setup

1. **Get API Key:**
* Sign up for a free API key at [Alpha Vantage](https://www.alphavantage.co/support/#api-key).

2. **Set Environment Variable:**
* Set the `ALPHA_VANTAGE_API_KEY` environment variable to your obtained key.
* **Linux/macOS:**
```bash
export ALPHA_VANTAGE_API_KEY='YOUR_API_KEY'
```
* **Windows (Command Prompt):**
```cmd
set ALPHA_VANTAGE_API_KEY=YOUR_API_KEY
```
* **Windows (PowerShell):**
```powershell
$env:ALPHA_VANTAGE_API_KEY='YOUR_API_KEY'
```
* Replace `YOUR_API_KEY` with the actual key. You might want to add this export command to your shell's profile file (like `.bashrc`, `.zshrc`, or PowerShell profile) for persistence.

## Running the Application

1. Navigate to the `goapp` directory in your terminal:
```bash
cd path/to/example-projects/goapp
```
2. Run the application:
```bash
go run main.go
```

The application will fetch the latest 5-minute interval data for the symbol defined in `main.go` (default: IBM) and print the symbol, last refresh time, and the latest closing price to the console.

## Note

- The free tier of Alpha Vantage has usage limits (e.g., 5 requests per minute, 500 per day). If you encounter issues, check if you've hit these limits.
- The application currently uses a hardcoded symbol ("IBM") and interval ("5min"). You can modify these constants in `main.go` if needed.
3 changes: 3 additions & 0 deletions goapp/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module goapp

go 1.17
121 changes: 121 additions & 0 deletions goapp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package main

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)

// Define structs to match the Alpha Vantage JSON structure
// Note: This structure might need adjustment based on the specific endpoint and data format.
// We are using TIME_SERIES_INTRADAY as an example.
type MetaData struct {
Information string `json:"1. Information"`
Symbol string `json:"2. Symbol"`
LastRefreshed string `json:"3. Last Refreshed"`
Interval string `json:"4. Interval"`
OutputSize string `json:"5. Output Size"`
TimeZone string `json:"6. Time Zone"`
}

type TimeSeriesEntry struct {
Open string `json:"1. open"`
High string `json:"2. high"`
Low string `json:"3. low"`
Close string `json:"4. close"`
Volume string `json:"5. volume"`
}

type AlphaVantageResponse struct {
MetaData MetaData `json:"Meta Data"`
TimeSeries5min map[string]TimeSeriesEntry `json:"Time Series (5min)"` // Key is timestamp string
}

const (
alphaVantageURL = "https://www.alphavantage.co/query"
function = "TIME_SERIES_INTRADAY"
symbol = "IBM" // Default symbol
interval = "5min"
)

func main() {
apiKey := os.Getenv("ALPHA_VANTAGE_API_KEY")
if apiKey == "" {
// Use a placeholder if the env var is not set
apiKey = "YOUR_API_KEY_HERE" // Replace with your actual key or keep as placeholder
log.Println("Warning: ALPHA_VANTAGE_API_KEY environment variable not set. Using placeholder.")
}

// Construct the API URL
// Example URL: https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=IBM&interval=5min&apikey=YOUR_API_KEY_HERE
requestURL := fmt.Sprintf("%s?function=%s&symbol=%s&interval=%s&apikey=%s",
alphaVantageURL, function, symbol, interval, apiKey)

log.Printf("Fetching data from: %s\n", alphaVantageURL) // Log base URL, not the full one with key

// Make the HTTP GET request
resp, err := http.Get(requestURL)
if err != nil {
log.Fatalf("Error fetching data: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
log.Fatalf("API request failed with status %s: %s", resp.Status, string(bodyBytes))
}

// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading response body: %v", err)
}

// Parse the JSON response
var apiResponse AlphaVantageResponse
err = json.Unmarshal(body, &apiResponse)
if err != nil {
// Log the body for debugging if JSON parsing fails
log.Printf("Raw response body:\n%s\n", string(body))
log.Fatalf("Error parsing JSON response: %v", err)
}

// Print some information
fmt.Printf("Stock Data for: %s\n", apiResponse.MetaData.Symbol)
fmt.Printf("Last Refreshed: %s\n", apiResponse.MetaData.LastRefreshed)

// Find the latest entry (map iteration order is not guaranteed, so find the latest timestamp)
var latestTime time.Time
var latestEntry TimeSeriesEntry
first := true

for timestampStr, entry := range apiResponse.TimeSeries5min {
// Example timestamp format from Alpha Vantage: "2023-10-27 19:55:00"
layout := "2006-01-02 15:04:05"
loc, _ := time.LoadLocation(apiResponse.MetaData.TimeZone) // Use timezone from metadata
currentTime, err := time.ParseInLocation(layout, timestampStr, loc)
if err != nil {
log.Printf("Warning: Could not parse timestamp '%s': %v", timestampStr, err)
continue // Skip if timestamp parsing fails
}

if first || currentTime.After(latestTime) {
latestTime = currentTime
latestEntry = entry
first = false
}
}

if !first { // Check if we found at least one entry
fmt.Printf("Latest Price (%s): %s\n", latestTime.Format(time.RFC3339), latestEntry.Close)
} else {
fmt.Println("No time series data found in the response.")
// This might happen if the API returns an error message within a 200 OK response,
// e.g., due to an invalid API key or hitting rate limits.
log.Printf("Raw response body for inspection:\n%s\n", string(body))
}
}
144 changes: 144 additions & 0 deletions goapp/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package main

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

// Sample JSON data mimicking the Alpha Vantage TIME_SERIES_INTRADAY response
const sampleJsonResponse = `
{
"Meta Data": {
"1. Information": "Intraday (5min) open, high, low, close prices and volume",
"2. Symbol": "TEST",
"3. Last Refreshed": "2025-04-20 11:20:00",
"4. Interval": "5min",
"5. Output Size": "Compact",
"6. Time Zone": "US/Eastern"
},
"Time Series (5min)": {
"2025-04-20 11:20:00": {
"1. open": "150.00",
"2. high": "150.50",
"3. low": "149.80",
"4. close": "150.25",
"5. volume": "10000"
},
"2025-04-20 11:15:00": {
"1. open": "149.80",
"2. high": "150.10",
"3. low": "149.70",
"4. close": "150.00",
"5. volume": "8500"
}
}
}
`

func TestParseAlphaVantageResponse(t *testing.T) {
var response AlphaVantageResponse
err := json.Unmarshal([]byte(sampleJsonResponse), &response)

// Test 1: Check if unmarshalling was successful
if err != nil {
t.Fatalf("Failed to unmarshal sample JSON: %v", err)
}

// Test 2: Check Meta Data fields
expectedSymbol := "TEST"
if response.MetaData.Symbol != expectedSymbol {
t.Errorf("Expected MetaData Symbol to be %s, but got %s", expectedSymbol, response.MetaData.Symbol)
}

expectedTimeZone := "US/Eastern"
if response.MetaData.TimeZone != expectedTimeZone {
t.Errorf("Expected MetaData Time Zone to be %s, but got %s", expectedTimeZone, response.MetaData.TimeZone)
}

// Test 3: Check if Time Series data exists and has the correct number of entries
expectedEntries := 2
if len(response.TimeSeries5min) != expectedEntries {
t.Errorf("Expected %d time series entries, but got %d", expectedEntries, len(response.TimeSeries5min))
}

// Test 4: Check specific values in a time series entry
timestampKey := "2025-04-20 11:20:00"
entry, ok := response.TimeSeries5min[timestampKey]
if !ok {
t.Fatalf("Expected time series entry for key %s not found", timestampKey)
}

expectedClose := "150.25"
if entry.Close != expectedClose {
t.Errorf("Expected close price for %s to be %s, but got %s", timestampKey, expectedClose, entry.Close)
}

expectedVolume := "10000"
if entry.Volume != expectedVolume {
t.Errorf("Expected volume for %s to be %s, but got %s", timestampKey, expectedVolume, entry.Volume)
}
}

// Helper function to find the latest entry (copied and adapted from main.go for potential future tests)
// Not directly tested by TestParseAlphaVantageResponse but could be used in other tests.
func findLatestEntry(response AlphaVantageResponse) (time.Time, TimeSeriesEntry, bool) {
var latestTime time.Time
var latestEntry TimeSeriesEntry
first := true
found := false

layout := "2006-01-02 15:04:05" // Matching the sample data format
loc, err := time.LoadLocation(response.MetaData.TimeZone)
if err != nil {
// Handle error loading location, perhaps default to UTC or log
loc = time.UTC // Fallback
}


for timestampStr, entry := range response.TimeSeries5min {
currentTime, err := time.ParseInLocation(layout, timestampStr, loc)
if err != nil {
continue // Skip if timestamp parsing fails
}

if first || currentTime.After(latestTime) {
latestTime = currentTime
latestEntry = entry
first = false
found = true
}
}
return latestTime, latestEntry, found
}

// Example of testing the findLatestEntry logic (Optional additional test)
func TestFindLatestEntry(t *testing.T) {
var response AlphaVantageResponse
err := json.Unmarshal([]byte(sampleJsonResponse), &response)
if err != nil {
t.Fatalf("Setup failed: Could not unmarshal sample JSON for TestFindLatestEntry: %v", err)
}

latestTime, latestEntry, found := findLatestEntry(response)

if !found {
t.Fatal("Expected to find a latest entry, but none was found.")
}

expectedTimestampStr := "2025-04-20 11:20:00"
expectedClose := "150.25"

layout := "2006-01-02 15:04:05"
loc, _ := time.LoadLocation("US/Eastern") // Use timezone from sample
expectedTime, _ := time.ParseInLocation(layout, expectedTimestampStr, loc)


if !latestTime.Equal(expectedTime) {
t.Errorf("Expected latest time to be %s, but got %s", expectedTime, latestTime)
}

if latestEntry.Close != expectedClose {
t.Errorf("Expected latest entry close price to be %s, but got %s", expectedClose, latestEntry.Close)
}
}
10 changes: 9 additions & 1 deletion javascriptapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,16 @@ A simple weather application that displays current weather conditions for any ci

## Testing

Run the test suite with coverage report:
Run the quality test suite with coverage report:
`npm test`

### Running Security Tests

This app includes a separate suite of security tests focused on injection attack scenarios. These tests ensure the backend API safely handles potentially malicious input.

To run the security tests, execute:
`npm run test:security`

## Running Locally

Start the development server:
Expand Down Expand Up @@ -94,3 +101,4 @@ Jest - Testing framework
SuperTest - HTTP testing library
ESLint - Code quality tool
Prettier - Code formatter

3 changes: 2 additions & 1 deletion javascriptapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"start": "node server.js",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"format": "prettier --write ."
"format": "prettier --write .",
"test:security": "jest tests/security.test.js"
},
"keywords": [],
"author": "",
Expand Down
Loading