Skip to content

Commit effe3d0

Browse files
author
Developer
committed
Add Location, update docs, update Makefile
1 parent a3ab333 commit effe3d0

22 files changed

+2731
-248
lines changed

Makefile

Lines changed: 72 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,77 @@
22
# Development shortcuts for Docker Compose commands
33
# All commands run inside Docker containers - no local Ruby/Rails required
44

5+
# Color definitions
6+
RED := \033[0;31m
7+
GREEN := \033[0;32m
8+
YELLOW := \033[0;33m
9+
BLUE := \033[0;34m
10+
PURPLE := \033[0;35m
11+
CYAN := \033[0;36m
12+
WHITE := \033[0;37m
13+
BOLD := \033[1m
14+
RESET := \033[0m
15+
516
.PHONY: help dev up down restart logs shell console test spec lint rubocop migrate seed reset rebuild clean yarn assets routes
617

718
# Default target - show help
819
help:
9-
@echo "StreamSource Development Commands:"
20+
@echo "$(BOLD)$(BLUE)🚀 StreamSource Development Commands$(RESET)"
1021
@echo ""
11-
@echo "Core Commands:"
12-
@echo " make dev - Start all services with asset watchers"
13-
@echo " make up - Start core services only (no asset watchers)"
14-
@echo " make down - Stop all services"
15-
@echo " make restart - Restart all services"
16-
@echo " make logs - Show logs (follow mode)"
17-
@echo " make status - Show container status"
22+
@echo "$(BOLD)$(GREEN)Core Commands:$(RESET)"
23+
@echo " $(CYAN)make dev$(RESET) - Start all services with asset watchers"
24+
@echo " $(CYAN)make up$(RESET) - Start core services only (no asset watchers)"
25+
@echo " $(CYAN)make down$(RESET) - Stop all services"
26+
@echo " $(CYAN)make restart$(RESET) - Restart all services"
27+
@echo " $(CYAN)make logs$(RESET) - Show logs (follow mode)"
28+
@echo " $(CYAN)make status$(RESET) - Show container status"
1829
@echo ""
19-
@echo "Development:"
20-
@echo " make shell - Open bash shell in web container"
21-
@echo " make console - Open Rails console"
22-
@echo " make routes - Show Rails routes"
23-
@echo " make attach - Attach to web container (for pry debugging)"
30+
@echo "$(BOLD)$(GREEN)Development:$(RESET)"
31+
@echo " $(CYAN)make shell$(RESET) - Open bash shell in web container"
32+
@echo " $(CYAN)make console$(RESET) - Open Rails console"
33+
@echo " $(CYAN)make routes$(RESET) - Show Rails routes"
34+
@echo " $(CYAN)make attach$(RESET) - Attach to web container (for pry debugging)"
2435
@echo ""
25-
@echo "Testing:"
26-
@echo " make test - Run full test suite (RSpec)"
27-
@echo " make spec - Run specific test (use file=path/to/spec.rb)"
28-
@echo " make coverage - Open test coverage report"
36+
@echo "$(BOLD)$(GREEN)Testing:$(RESET)"
37+
@echo " $(CYAN)make test$(RESET) - Run full test suite (RSpec)"
38+
@echo " $(CYAN)make spec$(RESET) - Run specific test (use $(YELLOW)file=path/to/spec.rb$(RESET))"
39+
@echo " $(CYAN)make coverage$(RESET) - Open test coverage report"
2940
@echo ""
30-
@echo "Database:"
31-
@echo " make migrate - Run database migrations"
32-
@echo " make rollback - Rollback last migration"
33-
@echo " make seed - Seed the database"
34-
@echo " make reset - Reset database (drop, create, migrate, seed)"
35-
@echo " make db-console - Open PostgreSQL console"
41+
@echo "$(BOLD)$(GREEN)Database:$(RESET)"
42+
@echo " $(CYAN)make migrate$(RESET) - Run database migrations"
43+
@echo " $(CYAN)make rollback$(RESET) - Rollback last migration"
44+
@echo " $(CYAN)make seed$(RESET) - Seed the database"
45+
@echo " $(CYAN)make reset$(RESET) - Reset database (drop, create, migrate, seed)"
46+
@echo " $(CYAN)make db-console$(RESET) - Open PostgreSQL console"
3647
@echo ""
37-
@echo "Code Quality:"
38-
@echo " make lint - Run RuboCop linter"
39-
@echo " make lint-fix - Auto-fix RuboCop issues"
40-
@echo " make security - Run Brakeman security analysis"
48+
@echo "$(BOLD)$(GREEN)Code Quality:$(RESET)"
49+
@echo " $(CYAN)make lint$(RESET) - Run RuboCop linter"
50+
@echo " $(CYAN)make lint-fix$(RESET) - Auto-fix RuboCop issues"
51+
@echo " $(CYAN)make security$(RESET) - Run Brakeman security analysis"
4152
@echo ""
42-
@echo "Assets:"
43-
@echo " make assets - Build all assets (JS and CSS)"
44-
@echo " make watch-js - Watch and rebuild JavaScript"
45-
@echo " make watch-css - Watch and rebuild CSS"
53+
@echo "$(BOLD)$(GREEN)Assets:$(RESET)"
54+
@echo " $(CYAN)make assets$(RESET) - Build all assets (JS and CSS)"
55+
@echo " $(CYAN)make watch-js$(RESET) - Watch and rebuild JavaScript"
56+
@echo " $(CYAN)make watch-css$(RESET) - Watch and rebuild CSS"
4657
@echo ""
47-
@echo "Maintenance:"
48-
@echo " make rebuild - Complete rebuild (clean + build + migrate + seed)"
49-
@echo " make clean - Remove containers, volumes, and orphans"
50-
@echo " make install - Install/update dependencies"
58+
@echo "$(BOLD)$(GREEN)Maintenance:$(RESET)"
59+
@echo " $(CYAN)make rebuild$(RESET) - Complete rebuild (clean + build + migrate + seed)"
60+
@echo " $(CYAN)make clean$(RESET) - Remove containers, volumes, and orphans"
61+
@echo " $(CYAN)make install$(RESET) - Install/update dependencies"
5162

5263
# Core Commands
5364
# Start development environment with asset watchers
5465
dev:
5566
docker compose --profile assets up -d
56-
@echo "StreamSource is running!"
57-
@echo "- API: http://localhost:3000"
58-
@echo "- Admin: http://localhost:3000/admin"
59-
@echo "- API Docs: http://localhost:3000/api-docs"
67+
@echo "$(BOLD)$(GREEN)StreamSource is running!$(RESET)"
68+
@echo "$(YELLOW)- API:$(RESET) http://localhost:3000"
69+
@echo "$(YELLOW)- Admin:$(RESET) http://localhost:3000/admin"
70+
@echo "$(YELLOW)- API Docs:$(RESET) http://localhost:3000/api-docs"
6071

6172
# Start core services only (no asset watchers)
6273
up:
6374
docker compose up -d
64-
@echo "StreamSource core services are running (no asset watchers)"
75+
@echo "$(BOLD)$(GREEN)StreamSource core services are running$(RESET) $(YELLOW)(no asset watchers)$(RESET)"
6576

6677
# Stop all services
6778
down:
@@ -103,16 +114,17 @@ test:
103114
# Run specific test file
104115
spec:
105116
@if [ -z "$(file)" ]; then \
106-
echo "Usage: make spec file=spec/models/stream_spec.rb"; \
107-
echo " or: make spec file=spec/models/stream_spec.rb:42"; \
117+
echo "$(RED)Error: No file specified$(RESET)"; \
118+
echo "$(YELLOW)Usage:$(RESET) make spec file=spec/models/stream_spec.rb"; \
119+
echo "$(YELLOW) or:$(RESET) make spec file=spec/models/stream_spec.rb:42"; \
108120
else \
109121
docker compose exec web bin/test $(file); \
110122
fi
111123

112124
# Open test coverage report
113125
coverage:
114-
@echo "Opening coverage report..."
115-
@open coverage/index.html 2>/dev/null || xdg-open coverage/index.html 2>/dev/null || echo "Coverage report at: coverage/index.html"
126+
@echo "$(BLUE)Opening coverage report...$(RESET)"
127+
@open coverage/index.html 2>/dev/null || xdg-open coverage/index.html 2>/dev/null || echo "$(YELLOW)Coverage report at:$(RESET) coverage/index.html"
116128

117129
# Database Management
118130
# Run database migrations
@@ -200,24 +212,25 @@ db-prepare:
200212
# Rails generators
201213
generate:
202214
@if [ -z "$(what)" ]; then \
203-
echo "Usage: make generate what='model Stream title:string'"; \
204-
echo " or: make generate what='controller Api::V1::Streams'"; \
205-
echo " or: make generate what='migration AddIndexToStreams'"; \
215+
echo "$(RED)Error: No generator specified$(RESET)"; \
216+
echo "$(YELLOW)Usage:$(RESET) make generate what='model Stream title:string'"; \
217+
echo "$(YELLOW) or:$(RESET) make generate what='controller Api::V1::Streams'"; \
218+
echo "$(YELLOW) or:$(RESET) make generate what='migration AddIndexToStreams'"; \
206219
else \
207220
docker compose exec web bin/rails generate $(what); \
208221
fi
209222

210223
# Initial setup for new developers
211224
setup:
212-
@echo "Setting up StreamSource development environment..."
225+
@echo "$(BOLD)$(BLUE)Setting up StreamSource development environment...$(RESET)"
213226
docker compose build
214227
docker compose up -d
215228
docker compose exec web bin/rails db:prepare
216229
@echo ""
217-
@echo "Setup complete! StreamSource is running at:"
218-
@echo "- API: http://localhost:3000"
219-
@echo "- Admin: http://localhost:3000/admin ([email protected] / Password123!)"
220-
@echo "- API Docs: http://localhost:3000/api-docs"
230+
@echo "$(BOLD)$(GREEN)Setup complete! StreamSource is running at:$(RESET)"
231+
@echo "$(YELLOW)- API:$(RESET) http://localhost:3000"
232+
@echo "$(YELLOW)- Admin:$(RESET) http://localhost:3000/admin $(PURPLE)([email protected] / Password123!)$(RESET)"
233+
@echo "$(YELLOW)- API Docs:$(RESET) http://localhost:3000/api-docs"
221234

222235
# Utility Commands
223236
.PHONY: exec run yarn bundle
@@ -275,14 +288,14 @@ ps-all:
275288

276289
# Health check
277290
health:
278-
@echo "Checking StreamSource health..."
291+
@echo "$(BOLD)$(BLUE)Checking StreamSource health...$(RESET)"
279292
@docker compose ps
280293
@echo ""
281-
@echo "Rails status:"
282-
@docker compose exec web bin/rails runner "puts 'Rails: OK'"
294+
@echo "$(YELLOW)Rails status:$(RESET)"
295+
@docker compose exec web bin/rails runner "puts 'Rails: $(GREEN)OK$(RESET)' if true"
283296
@echo ""
284-
@echo "Database status:"
285-
@docker compose exec web bin/rails runner "puts 'Database: ' + (ActiveRecord::Base.connection.active? ? 'OK' : 'ERROR')"
297+
@echo "$(YELLOW)Database status:$(RESET)"
298+
@docker compose exec web bin/rails runner "puts 'Database: ' + (ActiveRecord::Base.connection.active? ? '$(GREEN)OK$(RESET)' : '$(RED)ERROR$(RESET)')"
286299
@echo ""
287-
@echo "Redis status:"
288-
@docker compose exec web bin/rails runner "puts 'Redis: ' + (Redis.new.ping == 'PONG' ? 'OK' : 'ERROR')"
300+
@echo "$(YELLOW)Redis status:$(RESET)"
301+
@docker compose exec web bin/rails runner "puts 'Redis: ' + (Redis.new.ping == 'PONG' ? '$(GREEN)OK$(RESET)' : '$(RED)ERROR$(RESET)')"
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
module Api
2+
module V1
3+
class LocationsController < ApplicationController
4+
before_action :authenticate_user!
5+
before_action :set_location, only: [:show, :update, :destroy]
6+
7+
# GET /api/v1/locations
8+
def index
9+
@locations = Location.all
10+
@locations = @locations.search(params[:search]) if params[:search].present?
11+
@locations = @locations.by_country(params[:country]) if params[:country].present?
12+
@locations = @locations.by_state(params[:state]) if params[:state].present?
13+
@locations = @locations.ordered
14+
15+
@pagy, @locations = pagy(@locations)
16+
17+
render json: {
18+
locations: ActiveModelSerializers::SerializableResource.new(
19+
@locations,
20+
each_serializer: LocationSerializer
21+
),
22+
meta: pagy_metadata(@pagy)
23+
}
24+
end
25+
26+
# GET /api/v1/locations/all
27+
# Returns all locations without pagination for client-side validation
28+
def all
29+
@locations = Location.ordered.select(:id, :city, :state_province, :country, :normalized_name)
30+
31+
# Cache this response for 5 minutes
32+
expires_in 5.minutes, public: true
33+
34+
render json: {
35+
locations: @locations.map { |loc|
36+
{
37+
id: loc.id,
38+
city: loc.city,
39+
state_province: loc.state_province,
40+
country: loc.country,
41+
display_name: loc.display_name,
42+
normalized_name: loc.normalized_name
43+
}
44+
}
45+
}
46+
end
47+
48+
# GET /api/v1/locations/:id
49+
def show
50+
render json: @location, serializer: LocationSerializer
51+
end
52+
53+
# POST /api/v1/locations
54+
def create
55+
@location = Location.new(location_params)
56+
57+
if @location.save
58+
render json: @location, serializer: LocationSerializer, status: :created
59+
else
60+
render json: { error: "Validation failed", details: @location.errors }, status: :unprocessable_entity
61+
end
62+
end
63+
64+
# PATCH/PUT /api/v1/locations/:id
65+
def update
66+
if @location.update(location_params)
67+
render json: @location, serializer: LocationSerializer
68+
else
69+
render json: { error: "Validation failed", details: @location.errors }, status: :unprocessable_entity
70+
end
71+
end
72+
73+
# DELETE /api/v1/locations/:id
74+
def destroy
75+
if @location.streams.exists?
76+
render json: {
77+
error: "Cannot delete location",
78+
details: { base: ["Location is being used by #{@location.streams.count} stream(s)"] }
79+
}, status: :unprocessable_entity
80+
else
81+
@location.destroy
82+
head :no_content
83+
end
84+
end
85+
86+
private
87+
88+
def set_location
89+
@location = Location.find(params[:id])
90+
rescue ActiveRecord::RecordNotFound
91+
render json: { error: "Location not found" }, status: :not_found
92+
end
93+
94+
def location_params
95+
params.require(:location).permit(:city, :state_province, :region, :country, :latitude, :longitude)
96+
end
97+
end
98+
end
99+
end

app/controllers/api/v1/streams_controller.rb

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,14 @@ def show
4545
end
4646

4747
def create
48-
stream = current_user.streams.build(stream_params)
48+
stream = current_user.streams.build(stream_params.except(:location))
4949
authorize stream
5050

51+
# Handle location creation/lookup
52+
if location_params.present?
53+
stream.location = Location.find_or_create_from_params(location_params)
54+
end
55+
5156
if stream.save
5257
render json: stream, serializer: StreamSerializer, scope: serialization_scope, status: :created
5358
else
@@ -58,7 +63,14 @@ def create
5863
def update
5964
authorize @stream
6065

61-
if @stream.update(stream_params)
66+
# Handle location update
67+
if location_params.present?
68+
@stream.location = Location.find_or_create_from_params(location_params)
69+
elsif params.has_key?(:location) && params[:location].nil?
70+
@stream.location = nil
71+
end
72+
73+
if @stream.update(stream_params.except(:location))
6274
render json: @stream, serializer: StreamSerializer, scope: serialization_scope
6375
else
6476
render_error(@stream.errors.full_messages.join(', '), :unprocessable_entity)
@@ -186,7 +198,12 @@ def set_stream
186198

187199
def stream_params
188200
params.permit(:source, :link, :status, :platform, :orientation, :kind,
189-
:city, :state, :notes, :title, :posted_by)
201+
:city, :state, :notes, :title, :posted_by, :location_id)
202+
end
203+
204+
def location_params
205+
return nil unless params[:location].is_a?(Hash)
206+
params.require(:location).permit(:city, :state_province, :region, :country, :latitude, :longitude)
190207
end
191208
end
192209
end

0 commit comments

Comments
 (0)