feat: Initial observations endpoint

This commit is contained in:
2026-02-16 18:05:44 -05:00
parent c3ec4db852
commit 067e907718
10 changed files with 303 additions and 12 deletions

228
AGENTS.md Normal file
View File

@@ -0,0 +1,228 @@
# AGENTS.md - Yockboard Development Guide
This document provides guidelines for agentic coding agents working on the Yockboard Rails application.
## Project Overview
- **Framework**: Ruby on Rails 8.1.2
- **Ruby Version**: 3.4.7
- **Database**: PostgreSQL
- **Testing**: Minitest (Rails default)
- **JavaScript**: Turbo + Stimulus with Importmap
## Build / Lint / Test Commands
### Development Server
```bash
./bin/dev # Start development server (alias for bin/rails server)
bin/rails server -p 3000
```
### Running Tests
```bash
# Run all tests
bin/rails test
# Run a single test file
bin/rails test test/models/user_test.rb
# Run a single test by name
bin/rails test test/models/user_test.rb -n test_user_validity
# Run system tests (browser-based)
bin/rails test:system
# Run tests in specific directory
bin/rails test test/controllers/
```
### Linting & Code Style
```bash
# Run RuboCop linter
bin/rubocop
# Run specific RuboCop check
bin/rubocop app/models/
# Auto-fix RuboCop issues
bin/rubocop -a
```
### Security Scanning
```bash
# Scan for Rails security vulnerabilities
bin/brakeman --no-pager
# Scan for known gem vulnerabilities
bin/bundler-audit
# Audit JS dependencies
bin/importmap audit
```
### Database
```bash
bin/rails db:create db:migrate # Setup database
bin/rails db:test:prepare # Prepare test database
bin/rails db:reset # Reset database
```
### CI Pipeline (in .github/workflows/ci.yml)
- `scan_ruby`: Brakeman + bundler-audit
- `scan_js`: Importmap audit
- `lint`: RuboCop
- `test`: Full test suite with PostgreSQL
- `system-test`: System/integration tests
## Code Style Guidelines
### General Philosophy
Follow the **Rails Omakase** style (via `rubocop-rails-omakase`). This is the default Rails team style.
### File Organization
- Models: `app/models/`
- Controllers: `app/controllers/`
- Views: `app/views/controller_name/`
- Helpers: `app/helpers/`
- Jobs: `app/jobs/`
- Mailers: `app/mailers/`
### Naming Conventions
- **Classes/Modules**: `PascalCase` (e.g., `UserAccount`, `OrderProcessor`)
- **Database tables**: `snake_case_plurals` (e.g., `user_accounts`, `orders`)
- **Files**: `snake_case.rb` (e.g., `user_account.rb`, `order_processor.rb`)
- **Methods/variables**: `snake_case` (e.g., `user_accounts`, `process_order`)
- **Boolean methods**: End with `?` (e.g., `valid?`, `confirmed?`)
- **Bang methods**: End with `!` for mutating variants (e.g., `save!`, `update!`)
- **Query methods**: End with `?` or use plural names (e.g., `admin?`, `users`)
### Routing
- Use RESTful routes with `resources` when applicable
- Use namespace for admin/API routes
- Prefer shallow nesting: `resources :posts, shallow: true do resources :comments end`
### Models
- Inherit from `ApplicationRecord`
- Use concerns for shared behavior in `app/models/concerns/`
- Define associations explicitly
- Use scopes for common queries
```ruby
class User < ApplicationRecord
has_many :posts, dependent: :destroy
scope :active, -> { where(active: true) }
def full_name
"#{first_name} #{last_name}".strip
end
end
```
### Controllers
- Inherit from `ApplicationController`
- Use strong parameters for mass assignment
- Follow RESTful actions pattern
- Keep controllers thin; push logic to models/services
```ruby
class UsersController < ApplicationController
def index
@users = User.active.order(:created_at)
end
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: "User created"
else
render :new, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email)
end
end
```
### Views
- Use ERB with HTML
- Use Turbo Frames/Streams for SPA-like behavior
- Prefer Stimulus controllers over vanilla JS
- Use partials for reusable components
### Testing (Minitest)
- Test files go in `test/` mirroring `app/` structure
- Use `test/test_helper.rb` for shared setup
- Use fixtures in `test/fixtures/` for test data
```ruby
require "test_helper"
class UserTest < ActiveSupport::TestCase
test "valid user" do
user = User.new(email: "test@example.com", name: "Test")
assert user.valid?
end
test "invalid without email" do
user = User.new(name: "Test")
assert_not user.valid?
assert_includes user.errors[:email], "must be present"
end
end
```
### Error Handling
- Use ` rescue` blocks for expected errors
- Use `fail` or `raise` for unexpected states
- Flash messages for user feedback
- Render appropriate HTTP status codes
### Database
- Use migrations for schema changes: `bin/rails generate migration AddFieldToTable`
- Use `change` method for reversible migrations
- Add indexes for foreign keys and frequently queried columns
### Dependencies
- Add gems to appropriate groups in `Gemfile`
- Run `bundle install` after adding gems
- Use `Gemfile.lock` for reproducible builds
### Git Conventions
- Write clear, concise commit messages
- Create feature branches for new work
- Run linter and tests before committing
## Important File Locations
- Routes: `config/routes.rb`
- Application config: `config/application.rb`
- Environment configs: `config/environments/`
- Database config: `config/database.yml`
- Test config: `test/test_helper.rb`
## Common Tasks
### Generate New Resource
```bash
bin/rails generate scaffold ModelName field:type field:type
bin/rails db:migrate
```
### Run Specific Gem Command
```bash
bundle exec brakeman --no-pager
bundle exec rubocop
bundle exec rails test
```
## Notes
- This project uses **Solid** gems for cache/queue/cable (SolidCache, SolidQueue, SolidCable)
- Asset pipeline: Propshaft (Rails 8 default)
- JS: Importmap with Turbo and Stimulus
- Deployment: Kamal (see `.kamal/`)

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module API
class APIController < ApplicationController
skip_forgery_protection
end
end

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
module API
module V1
module Weather
class ObservationsController < APIController
def create
Rails.logger.debug 'Create new observation'
head :ok
end
end
end
end
end

View File

@@ -1,4 +1,4 @@
# Name of your application. Used to uniquely configure containers. # Name of your application. Used to uniquely configure containers.deploy
service: yockboard service: yockboard
# Name of the container image (use your-user/app-name on external registries). # Name of the container image (use your-user/app-name on external registries).
@@ -7,7 +7,7 @@ image: yockboard
# Deploy to these servers. # Deploy to these servers.
servers: servers:
web: web:
- 192.168.0.1 - 103.163.186.137
# job: # job:
# hosts: # hosts:
# - 192.168.0.1 # - 192.168.0.1
@@ -20,9 +20,9 @@ servers:
# #
# Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer). # Don't use this when deploying to multiple web servers (then you have to terminate SSL at your load balancer).
# #
# proxy: proxy:
# ssl: true ssl: true
# host: app.example.com host: yockboard.yock.dev
# Where you keep your container images. # Where you keep your container images.
registry: registry:
@@ -41,6 +41,7 @@ env:
secret: secret:
- RAILS_MASTER_KEY - RAILS_MASTER_KEY
clear: clear:
PORT: 3005
# Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.
# When you start using multiple servers, you should split out job processing to a dedicated machine. # When you start using multiple servers, you should split out job processing to a dedicated machine.
SOLID_QUEUE_IN_PUMA: true SOLID_QUEUE_IN_PUMA: true

View File

@@ -2,6 +2,9 @@ require "active_support/core_ext/integer/time"
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
config.hosts = [
"yockboard.yock.dev", # Allow requests from example.com
]
# Make code changes take effect immediately without server restart. # Make code changes take effect immediately without server restart.
config.enable_reloading = true config.enable_reloading = true

View File

@@ -80,10 +80,9 @@ Rails.application.configure do
config.active_record.attributes_for_inspect = [ :id ] config.active_record.attributes_for_inspect = [ :id ]
# Enable DNS rebinding protection and other `Host` header attacks. # Enable DNS rebinding protection and other `Host` header attacks.
# config.hosts = [ config.hosts = [
# "example.com", # Allow requests from example.com "yockboard.yock.dev", # Allow requests from example.com
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` ]
# ]
# #
# Skip DNS rebinding protection for the default health check endpoint. # Skip DNS rebinding protection for the default health check endpoint.
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } } # config.host_authorization = { exclude: ->(request) { request.path == "/up" } }

View File

@@ -11,6 +11,6 @@
# end # end
# These inflection rules are supported but not enabled by default: # These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect| ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym "RESTful" inflect.acronym 'API'
# end end

View File

@@ -11,4 +11,12 @@ Rails.application.routes.draw do
# Defines the root path route ("/") # Defines the root path route ("/")
# root "posts#index" # root "posts#index"
namespace :api do
namespace :v1 do
namespace :weather do
resources :observations
end
end
end
end end

View File

@@ -0,0 +1,8 @@
class CreateObservations < ActiveRecord::Migration[8.1]
def change
create_table :observations do |t|
t.jsonb :request_body
t.timestamps
end
end
end

22
db/schema.rb generated Normal file
View File

@@ -0,0 +1,22 @@
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2026_02_16_224032) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
create_table "observations", force: :cascade do |t|
t.datetime "created_at", null: false
t.jsonb "request_body"
t.datetime "updated_at", null: false
end
end