Planned and executed phase001
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# Agent Notes
|
||||
|
||||
## Project Shape
|
||||
- Rails 8.1 app (`CarTracker`) on Ruby `4.0.6`; `.ruby-version` includes the `ruby-` prefix, while the Dockerfile uses `ARG RUBY_VERSION=4.0.6`.
|
||||
- The app is still mostly generated Rails skeleton. Treat `DESIGN.md` as product direction, not proof of implemented models/routes.
|
||||
- Database is SQLite under `storage/`. Production is configured with separate SQLite databases for primary, Solid Cache, Solid Queue, and Solid Cable.
|
||||
- JavaScript uses importmap + Turbo + Stimulus; there is no `package.json` or npm install step. Pin JS dependencies with `bin/importmap`, not npm/yarn.
|
||||
|
||||
## Commands
|
||||
- Setup/update dependencies and DB: `bin/setup --skip-server`. Omit `--skip-server` only when you want it to start `bin/dev` afterward.
|
||||
- Run the app: `bin/dev` (it just execs `bin/rails server`).
|
||||
- Repo CI script: `bin/ci` runs setup, RuboCop, bundler-audit, importmap audit, and strict Brakeman. It does not run specs.
|
||||
- GitHub CI also runs Brakeman, bundler-audit, importmap audit, and RuboCop only; do not assume tests ran in CI.
|
||||
- Style check: `bin/rubocop` (`rubocop-rails-omakase` via `.rubocop.yml`).
|
||||
- Security checks: `bin/bundler-audit`, `bin/importmap audit`, `bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error`.
|
||||
|
||||
## Tests
|
||||
- RSpec is installed; run specs with `bundle exec rspec`, not `bin/rails test`.
|
||||
- Run a focused spec with `bundle exec rspec spec/path/to/file_spec.rb:LINE`.
|
||||
- `.rspec` only requires `spec_helper`; Rails specs need `require "rails_helper"` explicitly.
|
||||
- `DESIGN.md` says to develop features test-first. Add specs for new behavior even though current CI does not run them.
|
||||
|
||||
## Rails/Runtime Gotchas
|
||||
- `config/application.rb` disables Rails system test generation and does not load `rails/test_unit/railtie`.
|
||||
- `ApplicationController` rejects non-modern browsers with `allow_browser versions: :modern` and uses `stale_when_importmap_changes`.
|
||||
- Solid Queue jobs can run with `bin/jobs`; Kamal production config currently runs Solid Queue inside Puma via `SOLID_QUEUE_IN_PUMA: true`.
|
||||
- Production Docker is intended for Kamal/production, not local development; the entrypoint runs `bin/rails db:prepare` before starting the server.
|
||||
@@ -0,0 +1,153 @@
|
||||
# Vehicle Tracker Application Design
|
||||
|
||||
## Goal
|
||||
A web application to replace manual spreadsheet tracking of vehicle maintenance
|
||||
and fuel usage of a shared fleet.
|
||||
|
||||
## Architecture & Philosophy
|
||||
* **Language:** Ruby on Rails
|
||||
* **Style:** Snake_case
|
||||
* **Principle:** Keep it simple. Do not over-optimize. CRUD is the priority.
|
||||
* **Database:** Relational, tracking "Events" linked to specific "Entities."
|
||||
* **Tests:** We should be test driven in our development. No feature should
|
||||
be created without a test.
|
||||
|
||||
## Data models
|
||||
|
||||
### 1. User
|
||||
|
||||
This table will contain information about the users of the app. Mostly used to
|
||||
know who updated what. This table will include:
|
||||
|
||||
* *Fields:* `name`, `email`
|
||||
|
||||
|
||||
### 2. Vehicle
|
||||
|
||||
This table will contain information about the vehicles (assets) that we
|
||||
track. They have no strict ownership relationship as they can be used/driven by
|
||||
multiple operators. The vehicle information will include:
|
||||
|
||||
* *Fields:* `make`, `model`, `year`, `color`, `vin`,
|
||||
`licence_place`, `current_odometer`, `fuel_tank_size`
|
||||
|
||||
### 3. MaintenanceSchedule
|
||||
Configures the maintenance rules for a specific vehicle. This allows the
|
||||
"overdue" check to be automated by comparing the current schedule against the
|
||||
actual history.
|
||||
* *Fields:* `vehicle_id`, `maintenance_type`, `mileage_interval`,
|
||||
`time_interval`
|
||||
* *Relationship:* Belongs to Vehicle
|
||||
|
||||
### 4. FuelEntry
|
||||
Records specific refueling events.
|
||||
* *Fields:* `vehicle_id`, `odometer` (at time of fill), `gallons_pumped`,
|
||||
`price_paid`, `date`, `updated_by_user_id` (Tracks who submitted)
|
||||
* *Relationship:* Belongs to Vehicle
|
||||
|
||||
### 5. MainetnanceEntry
|
||||
Records maintenance and repair history.
|
||||
* *Fields:* `vehicle_id`, `odometer` (at time of service), `description`
|
||||
(Should exactly match a `maintenance_type` value from the
|
||||
MaintenanceSchedule), `cost`, `date`, `updated_by_user_id` (Tracks who
|
||||
submitted)
|
||||
* *Relationship:* Belongs to Vehicle
|
||||
|
||||
## Data Relationships
|
||||
|
||||
* **Vehicle** has_many **MaintenanceSchedules**
|
||||
* **Vehicle** has_many **FuelEntries**
|
||||
* **Vehicle** has_many **MaintenanceEntries**
|
||||
* **User** has_many **FuelEntries** (as the actor)
|
||||
* **User** has_many **MaintenanceEntries** (as the actor)
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Scenario A: Fueling
|
||||
1. User navigates to `Vehicle#show`.
|
||||
2. User fills form with Odometer, Gallons, Price.
|
||||
3. User submits -> `FuelEntry` is saved to DB.
|
||||
4. `current_odometer` is updated on the Vehicle record.
|
||||
|
||||
### Scenario B: Maintenance
|
||||
1. User navigates to `Vehicle#show`.
|
||||
2. User fills form with Description, Cost, Date.
|
||||
3. User submits -> `MaintenanceEntry` is saved to DB.
|
||||
|
||||
### Scenario C: Overdue Alerts
|
||||
1. On `Vehicle#show`, query the most recent `MaintenanceEntry`.
|
||||
2. Compare `MaintenanceEntry.date` vs `Date.today`.
|
||||
3. If difference > 6 months, display alert message to User.
|
||||
|
||||
Note that there is in the MaintenanceSchedule data, there is a `time_interval`
|
||||
field that defines the length of time for maintenance. There is also a milage
|
||||
that should be used as well and it is an or operation for them.
|
||||
|
||||
## User Authentication
|
||||
|
||||
Because this is an internal application, I plan on something like this:
|
||||
|
||||
The server will every month generate a key. This key will be printed out in a
|
||||
QR code format. The users will then scan the QR code to collect the key. When
|
||||
they make a request, this key is sent along with it and is used to verify the
|
||||
user. Ideally each user would use the QR code to prove that I know them, enter
|
||||
a name and then get a session token that is used instead of sending the raw QR
|
||||
code. The idea is that it is proof that I know who they are by them posessing
|
||||
information that only I have (the QR code) and then in the future they should
|
||||
use the token to prove who they are.
|
||||
|
||||
Again, because this is an internal application, there should not be any
|
||||
passwords. Users are not really created to be logged in, more because I need to
|
||||
track who is doing what with the equipment.
|
||||
|
||||
## Database choice
|
||||
|
||||
I have no hard requirements. I would prefer the database to be easy to use for
|
||||
development. This application will really only ever have up to 100 users so I
|
||||
do not need high performance. SQLite should work fantastic in this case.
|
||||
|
||||
## Test framework
|
||||
|
||||
The test framework is rspec. You can run the tests with `bin/rails test`
|
||||
|
||||
## Vehicle ownership
|
||||
|
||||
Vehicles can be used by multiple operators. We do not need to track who is
|
||||
driving though. We just want to know about who is submitting updates and
|
||||
enteries.
|
||||
|
||||
## Rails information
|
||||
|
||||
```
|
||||
$ rails --version
|
||||
Rails 8.1.3.1
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
This will be a local development deployment. Once we are ready, it will be
|
||||
packaged into a Docker container and put on a production server. This container
|
||||
is designed to run behind a reverse proxy that is already in place.
|
||||
|
||||
|
||||
# Final notes
|
||||
|
||||
The relationship between the user and vehicle is minimal. This is on purpose
|
||||
because we really are only using the user to track who is making changes. We do
|
||||
not really care if it is the actual driver or not.
|
||||
|
||||
For overdue alerts, we will send notifications to the users using a
|
||||
notification service. This is not yet set up so we will not worry about it. We
|
||||
can leave stubs though for that to happen. The notification service just
|
||||
requires a curl request so it is simple to add later. For now, just log it into
|
||||
the console so that we can test it is working.
|
||||
|
||||
For data access, it would be nice to have 'admin' and 'regular' users however
|
||||
that is a distinction that we do not yet need. Remember to keep things simple
|
||||
but flexible in the future. Do not wall us in with how code is done.
|
||||
|
||||
Each edit by the users will be a seperate database entry. There should be no
|
||||
user editing the same entry at any time. The only possible issue that I can see
|
||||
is if we have two users in the same vehicle and they both submit a fuel
|
||||
entry. In this case, we would just delete one of the entries. At no point in
|
||||
time should two users be editing the same record.
|
||||
@@ -41,6 +41,11 @@ gem "thruster", require: false
|
||||
gem "image_processing", "~> 1.2"
|
||||
|
||||
group :development, :test do
|
||||
# Testing frameworks
|
||||
gem "rspec-rails"
|
||||
gem "factory_bot_rails"
|
||||
gem "capybara"
|
||||
|
||||
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
|
||||
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
|
||||
|
||||
@@ -50,6 +55,11 @@ group :development, :test do
|
||||
# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
|
||||
gem "brakeman", require: false
|
||||
|
||||
gem "rubocop"
|
||||
gem "rubocop-rails"
|
||||
gem "rubocop-rspec"
|
||||
gem "rubocop-rspec_rails"
|
||||
|
||||
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
|
||||
gem "rubocop-rails-omakase", require: false
|
||||
end
|
||||
|
||||
@@ -75,6 +75,8 @@ GEM
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
uri (>= 0.13.1)
|
||||
addressable (2.9.0)
|
||||
public_suffix (>= 2.0.2, < 8.0)
|
||||
ast (2.4.3)
|
||||
base64 (0.3.0)
|
||||
bcrypt_pbkdf (1.1.2)
|
||||
@@ -88,6 +90,15 @@ GEM
|
||||
bundler-audit (0.9.3)
|
||||
bundler (>= 1.2.0)
|
||||
thor (~> 1.0)
|
||||
capybara (3.40.0)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.11)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
concurrent-ruby (1.3.8)
|
||||
connection_pool (3.0.2)
|
||||
crass (1.0.7)
|
||||
@@ -95,6 +106,7 @@ GEM
|
||||
debug (1.11.1)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
diff-lcs (1.6.2)
|
||||
dotenv (3.2.0)
|
||||
drb (2.2.3)
|
||||
ed25519 (1.4.0)
|
||||
@@ -102,6 +114,11 @@ GEM
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.4.2)
|
||||
tzinfo
|
||||
factory_bot (6.6.0)
|
||||
activesupport (>= 6.1.0)
|
||||
factory_bot_rails (6.5.1)
|
||||
factory_bot (~> 6.5)
|
||||
railties (>= 6.1.0)
|
||||
ffi (1.17.4-aarch64-linux-gnu)
|
||||
ffi (1.17.4-aarch64-linux-musl)
|
||||
ffi (1.17.4-arm-linux-gnu)
|
||||
@@ -156,6 +173,7 @@ GEM
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.2.1)
|
||||
matrix (0.4.3)
|
||||
mini_magick (5.4.0)
|
||||
logger
|
||||
mini_mime (1.1.5)
|
||||
@@ -203,6 +221,7 @@ GEM
|
||||
actionpack (>= 7.0.0)
|
||||
activesupport (>= 7.0.0)
|
||||
rack
|
||||
public_suffix (7.0.5)
|
||||
puma (8.0.2)
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.5.0)
|
||||
@@ -259,6 +278,23 @@ GEM
|
||||
regexp_parser (2.12.0)
|
||||
reline (0.7.0)
|
||||
io-console (~> 0.5)
|
||||
rspec-core (3.13.6)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.5)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-mocks (3.13.8)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (8.0.4)
|
||||
actionpack (>= 7.2)
|
||||
activesupport (>= 7.2)
|
||||
railties (>= 7.2)
|
||||
rspec-core (>= 3.13.0, < 5.0.0)
|
||||
rspec-expectations (>= 3.13.0, < 5.0.0)
|
||||
rspec-mocks (>= 3.13.0, < 5.0.0)
|
||||
rspec-support (>= 3.13.0, < 5.0.0)
|
||||
rspec-support (3.13.7)
|
||||
rubocop (1.91.0)
|
||||
json (>= 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
@@ -287,6 +323,14 @@ GEM
|
||||
rubocop (>= 1.72)
|
||||
rubocop-performance (>= 1.24)
|
||||
rubocop-rails (>= 2.30)
|
||||
rubocop-rspec (3.10.2)
|
||||
lint_roller (~> 1.1)
|
||||
regexp_parser (>= 2.0)
|
||||
rubocop (~> 1.86, >= 1.86.2)
|
||||
rubocop-rspec_rails (2.32.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec (~> 3.5)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-vips (2.3.0)
|
||||
ffi (~> 1.12)
|
||||
@@ -347,6 +391,8 @@ GEM
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.8.3)
|
||||
|
||||
PLATFORMS
|
||||
@@ -363,7 +409,9 @@ DEPENDENCIES
|
||||
bootsnap
|
||||
brakeman
|
||||
bundler-audit
|
||||
capybara
|
||||
debug
|
||||
factory_bot_rails
|
||||
image_processing (~> 1.2)
|
||||
importmap-rails
|
||||
jbuilder
|
||||
@@ -371,7 +419,12 @@ DEPENDENCIES
|
||||
propshaft
|
||||
puma (>= 5.0)
|
||||
rails (~> 8.1.3, >= 8.1.3.1)
|
||||
rspec-rails
|
||||
rubocop
|
||||
rubocop-rails
|
||||
rubocop-rails-omakase
|
||||
rubocop-rspec
|
||||
rubocop-rspec_rails
|
||||
solid_cable
|
||||
solid_cache
|
||||
solid_queue
|
||||
@@ -395,6 +448,7 @@ CHECKSUMS
|
||||
activerecord (8.1.3.1) sha256=0a2fb6c28f4938f6b013a3a549bec0a7e37d535f3dc8990e804bcc3258c0403b
|
||||
activestorage (8.1.3.1) sha256=f555254f387b1cffa499d2fd3115d12635eadc5b15206a8534316a67036163ef
|
||||
activesupport (8.1.3.1) sha256=85458765f25ea48b9019c46b6bb3fa5683197bf4280d9f06710a6e8d7a831376
|
||||
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
|
||||
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
||||
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
||||
bcrypt_pbkdf (1.1.2) sha256=c2414c23ce66869b3eb9f643d6a3374d8322dfb5078125c82792304c10b94cf6
|
||||
@@ -404,17 +458,21 @@ CHECKSUMS
|
||||
brakeman (8.0.6) sha256=759cc69341115e6c2dcd47b6fd8649a0b9bd540e3585ac8a0a94e31c66fee386
|
||||
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
|
||||
bundler-audit (0.9.3) sha256=81c8766c71e47d0d28a0f98c7eed028539f21a6ea3cd8f685eb6f42333c9b4e9
|
||||
capybara (3.40.0) sha256=42dba720578ea1ca65fd7a41d163dd368502c191804558f6e0f71b391054aeef
|
||||
concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
|
||||
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
||||
crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295
|
||||
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
|
||||
debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
|
||||
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
||||
dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d
|
||||
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
||||
ed25519 (1.4.0) sha256=16e97f5198689a154247169f3453ef4cfd3f7a47481fde0ae33206cdfdcac506
|
||||
erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92
|
||||
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
|
||||
et-orbi (1.4.2) sha256=bb555dae668419cb24caa2a293a170e58be6d4df1e017c51f5030bdc133cd20c
|
||||
factory_bot (6.6.0) sha256=1fc1b3b5620ec980a6a27aec1b6ec8c250ca82962e970e8a40f93e8d388d4b89
|
||||
factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68
|
||||
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
|
||||
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
|
||||
ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564
|
||||
@@ -437,6 +495,7 @@ CHECKSUMS
|
||||
loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918
|
||||
mail (2.9.1) sha256=06574eca475253d6c18145dd70af80d0eb970182d55053497c5f4d797ea160e8
|
||||
marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f
|
||||
matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b
|
||||
mini_magick (5.4.0) sha256=f120af581d9ed4ec52c57f35a67a605d112bb1d8f582d1415b147fda42d11d78
|
||||
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
||||
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
|
||||
@@ -462,6 +521,7 @@ CHECKSUMS
|
||||
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
|
||||
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
||||
propshaft (1.3.2) sha256=1d56a3e56a92c21bfc29caf07406b5386b00d4c47ddf357cf989a5a234b1389e
|
||||
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
|
||||
puma (8.0.2) sha256=c8ed871dfbbe66448ea9ffd46692342d9804d4071522b52b5331b7b6e7b686fb
|
||||
raabro (1.5.0) sha256=3f998a7bc84f9c84df3ab580634d2e0a5bda4f0841168d56035f529c9877440a
|
||||
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
||||
@@ -479,11 +539,18 @@ CHECKSUMS
|
||||
rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
|
||||
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
|
||||
reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d
|
||||
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
||||
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
||||
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
||||
rspec-rails (8.0.4) sha256=06235692fc0892683d3d34977e081db867434b3a24ae0dd0c6f3516bad4e22df
|
||||
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
||||
rubocop (1.91.0) sha256=9c82b7bf391c5d7e3798c5b9996e22a1fe3bd7468e351dfdeb96140c058296d0
|
||||
rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
|
||||
rubocop-performance (1.27.0) sha256=eeeb1374d062a368ee1c787b70eb0b0cc4b184cb1f8565f424760946146d61ce
|
||||
rubocop-rails (2.37.0) sha256=6e1645add5060e0328f8ddda0d820f55697c591394398bf14bb9dccb62f14b7e
|
||||
rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d
|
||||
rubocop-rspec (3.10.2) sha256=0b3e2ecc592cd10ecbf0095bb58d1e357905276e069643523cc19eb7495f65e2
|
||||
rubocop-rspec_rails (2.32.0) sha256=4a0d641c72f6ebb957534f539d9d0a62c47abd8ce0d0aeee1ef4701e892a9100
|
||||
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
||||
ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374
|
||||
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
|
||||
@@ -513,6 +580,7 @@ CHECKSUMS
|
||||
web-console (4.3.0) sha256=e13b71301cdfc2093f155b5aa3a622db80b4672d1f2f713119cc7ec7ac6a6da4
|
||||
websocket-driver (0.8.2) sha256=97c556b019bf3410b4961002ac501621e9322d3f8a7bc02161a09301cc4c4146
|
||||
websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
|
||||
xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e
|
||||
zeitwerk (2.8.3) sha256=2c85125a8467ce069e20123d1e709a08955c9d29c118c25b46b7b7fafdbb92e5
|
||||
|
||||
BUNDLED WITH
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# About me
|
||||
|
||||
I am familar in these languages:
|
||||
|
||||
- Python
|
||||
- Ruby
|
||||
- C/C++
|
||||
- VHDL
|
||||
|
||||
I have built multiple applications in these languages. Here are some examples
|
||||
of what I have designed and built:
|
||||
|
||||
Using Ruby on Rails: A ticket framework that integrates with Stripe to be able
|
||||
to sell tickets to concerts.
|
||||
|
||||
Using Flask/Django: A fiber optic network tracker that allows for clients to
|
||||
update the connections in a fiber optic network based on the connections in
|
||||
cases. The program is capable of auto routing new connections through boxes and
|
||||
tracks who makes what changes for history of work.
|
||||
|
||||
Using C/C++: Many embedded projects. A bluetooth beacon tracker bot that can
|
||||
follow a bluetooth device. A project that integrates with an FPGA using UART
|
||||
for communication. Uses the FPGA for real time controll and data collection
|
||||
while the C/C++ code is used for navigation and decision making
|
||||
|
||||
|
||||
# Rules I have
|
||||
|
||||
Keep things simple. There is no need to overcomplicate the project that we are
|
||||
working on. Yes we should be aware of future growth however "premature
|
||||
optimization is the root of all evil" We should not be careless in how we
|
||||
create and start building however we do not need to plan for every eventuality.
|
||||
|
||||
|
||||
# Personal preferences
|
||||
|
||||
I use Emacs for my editor. I strongly prefer open source toolchains. Personally
|
||||
I prefer snake_case unless the language convention specifies otherwise. My
|
||||
favorite language is Ruby.
|
||||
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
# README
|
||||
# Car Tracker
|
||||
|
||||
This README would normally document whatever steps are necessary to get the
|
||||
application up and running.
|
||||
Vehicle maintenance and fuel tracking application.
|
||||
|
||||
Things you may want to cover:
|
||||
## Testing
|
||||
|
||||
* Ruby version
|
||||
Run the application test suite with:
|
||||
|
||||
* System dependencies
|
||||
```sh
|
||||
bundle exec rspec
|
||||
```
|
||||
|
||||
* Configuration
|
||||
Run the full local CI baseline with:
|
||||
|
||||
* Database creation
|
||||
|
||||
* Database initialization
|
||||
|
||||
* How to run the test suite
|
||||
|
||||
* Services (job queues, cache servers, search engines, etc.)
|
||||
|
||||
* Deployment instructions
|
||||
|
||||
* ...
|
||||
```sh
|
||||
bin/ci
|
||||
```
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
class HomeController < ApplicationController
|
||||
def index
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
<main>
|
||||
<h1>Car Tracker</h1>
|
||||
<p>Vehicle maintenance and fuel tracking will be added here.</p>
|
||||
</main>
|
||||
@@ -3,6 +3,8 @@
|
||||
CI.run do
|
||||
step "Setup", "bin/setup --skip-server"
|
||||
|
||||
step "Test: RSpec", "bundle exec rspec"
|
||||
|
||||
step "Style: Ruby", "bin/rubocop"
|
||||
|
||||
step "Security: Gem audit", "bin/bundler-audit"
|
||||
|
||||
+1
-2
@@ -9,6 +9,5 @@ Rails.application.routes.draw do
|
||||
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
|
||||
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
|
||||
|
||||
# Defines the root path route ("/")
|
||||
# root "posts#index"
|
||||
root "home#index"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Phase 001 Execution
|
||||
|
||||
## Scope
|
||||
- Prepared the Rails/RSpec project baseline before adding application models.
|
||||
- Added a minimal visible root page for the application.
|
||||
- Documented the project test command.
|
||||
|
||||
## Changes
|
||||
- Confirmed `rspec-rails` and `factory_bot_rails` are present in the Gemfile.
|
||||
- Added `capybara` for system spec support.
|
||||
- Enabled RSpec spec type inference from file location.
|
||||
- Included FactoryBot syntax methods in RSpec configuration.
|
||||
- Configured system specs to use Capybara's `rack_test` driver by default.
|
||||
- Added `HomeController#index` as a placeholder landing page.
|
||||
- Set the root route to `home#index`.
|
||||
- Added a minimal root page view.
|
||||
- Added a request spec for `GET /`.
|
||||
- Added a system spec for the placeholder landing page.
|
||||
- Added `bundle exec rspec` to the CI sequence.
|
||||
- Updated `README.md` with the RSpec command and local CI command.
|
||||
|
||||
## Decisions
|
||||
- FactoryBot is the preferred future test data setup because it is already in the Gemfile.
|
||||
- Capybara is required because `rspec-rails` system specs depend on it for browser-style interactions.
|
||||
- `rack_test` is enough for the baseline placeholder system spec and avoids requiring Selenium before JavaScript browser coverage is needed.
|
||||
- No model specs were added because there are no real application models yet.
|
||||
- The root page is intentionally plain because Phase 001 only requires a visible landing point.
|
||||
- The documented test command is `bundle exec rspec`, matching project agent notes.
|
||||
|
||||
## Verification
|
||||
- Initial `bundle exec rspec` failed because `capybara` was not in the bundle.
|
||||
- After adding `capybara`, `bundle exec rspec` failed because the default system spec driver expected `selenium-webdriver`.
|
||||
- Configured system specs to use `rack_test`, then reran verification.
|
||||
- Passed: `bundle exec rspec`
|
||||
- Passed: `bin/rubocop`
|
||||
- Passed: `bin/ci`
|
||||
@@ -0,0 +1,21 @@
|
||||
# Phase 001: Project Baseline And Testing Setup
|
||||
|
||||
## Goal
|
||||
Establish a stable Rails/RSpec foundation before adding application behavior.
|
||||
|
||||
## Work
|
||||
- Confirm `rspec-rails` is installed and usable.
|
||||
- Ensure `bin/rails spec` or the project-preferred test command works.
|
||||
- Add initial request/system/model spec patterns for future phases.
|
||||
- Decide whether the app should use Rails fixtures or FactoryBot; prefer FactoryBot because it is already in the Gemfile.
|
||||
- Add a simple root page placeholder so the app has a visible landing point.
|
||||
|
||||
## Deliverables
|
||||
- Working test suite.
|
||||
- Root route and minimal home page.
|
||||
- Documented test command.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Test suite passes.
|
||||
- Root path returns success.
|
||||
- No application models are added without corresponding specs.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Phase 002: Core Data Model
|
||||
|
||||
## Goal
|
||||
Create the relational model foundation for users, vehicles, fuel entries, maintenance entries, and maintenance schedules.
|
||||
|
||||
## Work
|
||||
- Create `User` with `name` and `email`.
|
||||
- Create `Vehicle` with `make`, `model`, `year`, `color`, `vin`, `licence_plate`, `current_odometer`, and `fuel_tank_size`.
|
||||
- Create `MaintenanceSchedule` belonging to `Vehicle`.
|
||||
- Create `FuelEntry` belonging to `Vehicle` and `User` as `updated_by_user`.
|
||||
- Create `MaintenanceEntry` belonging to `Vehicle` and `User` as `updated_by_user`.
|
||||
- Add validations for required fields and basic numeric constraints.
|
||||
- Add model specs for associations, validations, and simple behavior.
|
||||
|
||||
## Notes
|
||||
- Correct likely typo from design: `licence_place` should probably be `licence_plate`.
|
||||
- Correct likely typo from design: `MainetnanceEntry` should be `MaintenanceEntry`.
|
||||
|
||||
## Deliverables
|
||||
- Database migrations.
|
||||
- ActiveRecord models.
|
||||
- Model specs.
|
||||
- Factories.
|
||||
|
||||
## Acceptance Criteria
|
||||
- All model specs pass.
|
||||
- Associations match the design.
|
||||
- Invalid records are rejected with useful validation errors.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Phase 003: Vehicle CRUD And Detail Page
|
||||
|
||||
## Goal
|
||||
Build the main vehicle management interface.
|
||||
|
||||
## Work
|
||||
- Add `VehiclesController`.
|
||||
- Add routes for vehicle index, show, new, create, edit, update, and destroy.
|
||||
- Build simple ERB views for vehicle CRUD.
|
||||
- Make `vehicles#index` the root page.
|
||||
- On `vehicles#show`, display vehicle details, maintenance schedules, fuel history, and maintenance history.
|
||||
- Keep layout simple and mobile-friendly.
|
||||
|
||||
## Deliverables
|
||||
- Vehicle CRUD UI.
|
||||
- Vehicle request specs.
|
||||
- Basic navigation.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Users can create, view, update, and delete vehicles.
|
||||
- Vehicle show page is the main operational screen.
|
||||
- Request specs cover successful and invalid create/update paths.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Phase 004: Fuel And Maintenance Event Entry
|
||||
|
||||
## Goal
|
||||
Allow users to record fuel and maintenance events from the vehicle detail page.
|
||||
|
||||
## Work
|
||||
- Add nested routes under vehicles for fuel entries and maintenance entries.
|
||||
- Add create forms on `vehicles#show`.
|
||||
- Save each fuel submission as a new `FuelEntry`.
|
||||
- Save each maintenance submission as a new `MaintenanceEntry`.
|
||||
- Update `Vehicle.current_odometer` when a fuel entry is created if the submitted odometer is newer.
|
||||
- Track `updated_by_user_id` on each event.
|
||||
- Add request specs and model specs for event creation.
|
||||
|
||||
## Deliverables
|
||||
- Fuel entry creation flow.
|
||||
- Maintenance entry creation flow.
|
||||
- Odometer update behavior.
|
||||
- Event history display on vehicle show page.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Fuel submissions create records and update vehicle odometer.
|
||||
- Maintenance submissions create records.
|
||||
- Invalid submissions re-render or redirect with validation errors.
|
||||
- Every event records the submitting user.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Phase 005: Lightweight Internal Authentication
|
||||
|
||||
## Goal
|
||||
Implement simple internal identity tracking without passwords.
|
||||
|
||||
## Work
|
||||
- Add a monthly access key concept for initial identification.
|
||||
- Add a flow where a person enters/scans the current key, provides name/email, and receives a session.
|
||||
- Store only a session user id/token in the browser after verification.
|
||||
- Add `Current.user` or controller helper methods for accessing the active user.
|
||||
- Require an identified user before creating fuel or maintenance entries.
|
||||
- Keep admin/regular user roles out of scope for now.
|
||||
|
||||
## Deliverables
|
||||
- Login/identification page.
|
||||
- Session creation and clearing.
|
||||
- User creation or lookup by email.
|
||||
- Controller specs/request specs for authenticated and unauthenticated flows.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Passwords are not used.
|
||||
- Raw QR/monthly key is not required on every request after session creation.
|
||||
- Fuel and maintenance submissions are attributed to the active user.
|
||||
- Unidentified users cannot submit entries.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Phase 006: Maintenance Schedules And Overdue Alerts
|
||||
|
||||
## Goal
|
||||
Use maintenance schedules and event history to show overdue maintenance status.
|
||||
|
||||
## Work
|
||||
- Add CRUD for vehicle maintenance schedules.
|
||||
- On `vehicles#show`, compare each schedule against maintenance history.
|
||||
- Mark a schedule overdue when either mileage interval or time interval is exceeded.
|
||||
- Use the matching maintenance entry description/type as the history key.
|
||||
- Log overdue notifications to Rails logger for now.
|
||||
- Keep external notification service integration stubbed and isolated.
|
||||
|
||||
## Deliverables
|
||||
- Maintenance schedule management.
|
||||
- Overdue calculation logic.
|
||||
- Vehicle show alerts.
|
||||
- Specs for mileage-based, time-based, and not-overdue cases.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Vehicle show page displays overdue alerts.
|
||||
- Mileage and time intervals are treated as an OR condition.
|
||||
- Notification behavior is logged, not sent externally.
|
||||
- Overdue logic is covered by tests.
|
||||
@@ -0,0 +1,77 @@
|
||||
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
||||
require 'spec_helper'
|
||||
ENV['RAILS_ENV'] ||= 'test'
|
||||
require_relative '../config/environment'
|
||||
# Prevent database truncation if the environment is production
|
||||
abort("The Rails environment is running in production mode!") if Rails.env.production?
|
||||
# Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file
|
||||
# that will avoid rails generators crashing because migrations haven't been run yet
|
||||
# return unless Rails.env.test?
|
||||
require 'rspec/rails'
|
||||
# Add additional requires below this line. Rails is not loaded until this point!
|
||||
|
||||
# Requires supporting ruby files with custom matchers and macros, etc, in
|
||||
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
||||
# run as spec files by default. This means that files in spec/support that end
|
||||
# in _spec.rb will both be required and run as specs, causing the specs to be
|
||||
# run twice. It is recommended that you do not name files matching this glob to
|
||||
# end with _spec.rb. You can configure this pattern with the --pattern
|
||||
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
|
||||
#
|
||||
# The following line is provided for convenience purposes. It has the downside
|
||||
# of increasing the boot-up time by auto-requiring all files in the support
|
||||
# directory. Alternatively, in the individual `*_spec.rb` files, manually
|
||||
# require only the support files necessary.
|
||||
#
|
||||
# Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }
|
||||
|
||||
# Ensures that the test database schema matches the current schema file.
|
||||
# If there are pending migrations it will invoke `db:test:prepare` to
|
||||
# recreate the test database by loading the schema.
|
||||
# If you are not using ActiveRecord, you can remove these lines.
|
||||
begin
|
||||
ActiveRecord::Migration.maintain_test_schema!
|
||||
rescue ActiveRecord::PendingMigrationError => e
|
||||
abort e.to_s.strip
|
||||
end
|
||||
RSpec.configure do |config|
|
||||
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
||||
config.fixture_paths = [
|
||||
Rails.root.join('spec/fixtures')
|
||||
]
|
||||
|
||||
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
||||
# examples within a transaction, remove the following line or assign false
|
||||
# instead of true.
|
||||
config.use_transactional_fixtures = true
|
||||
|
||||
# You can uncomment this line to turn off ActiveRecord support entirely.
|
||||
# config.use_active_record = false
|
||||
|
||||
# RSpec Rails uses metadata to mix in different behaviours to your tests,
|
||||
# for example enabling you to call `get` and `post` in request specs. e.g.:
|
||||
#
|
||||
# RSpec.describe UsersController, type: :request do
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# The different available types are documented in the features, such as in
|
||||
# https://rspec.info/features/8-0/rspec-rails
|
||||
#
|
||||
# You can also infer these behaviours automatically by location, e.g.
|
||||
# /spec/models would pull in the same behaviour as `type: :model` but this
|
||||
# behaviour is considered legacy and will be removed in a future version.
|
||||
#
|
||||
config.infer_spec_type_from_file_location!
|
||||
|
||||
config.include FactoryBot::Syntax::Methods
|
||||
|
||||
config.before(:each, type: :system) do
|
||||
driven_by :rack_test
|
||||
end
|
||||
|
||||
# Filter lines from Rails gems in backtraces.
|
||||
config.filter_rails_from_backtrace!
|
||||
# arbitrary gems may also be filtered via:
|
||||
# config.filter_gems_from_backtrace("gem name")
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Home" do
|
||||
describe "GET /" do
|
||||
it "returns a successful response" do
|
||||
get root_path
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include("Car Tracker")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
|
||||
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
||||
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
||||
# this file to always be loaded, without a need to explicitly require it in any
|
||||
# files.
|
||||
#
|
||||
# Given that it is always loaded, you are encouraged to keep this file as
|
||||
# light-weight as possible. Requiring heavyweight dependencies from this file
|
||||
# will add to the boot time of your test suite on EVERY test run, even for an
|
||||
# individual file that may not need all of that loaded. Instead, consider making
|
||||
# a separate helper file that requires the additional dependencies and performs
|
||||
# the additional setup, and require it from the spec files that actually need
|
||||
# it.
|
||||
#
|
||||
# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
||||
RSpec.configure do |config|
|
||||
# rspec-expectations config goes here. You can use an alternate
|
||||
# assertion/expectation library such as wrong or the stdlib/minitest
|
||||
# assertions if you prefer.
|
||||
config.expect_with :rspec do |expectations|
|
||||
# This option will default to `true` in RSpec 4. It makes the `description`
|
||||
# and `failure_message` of custom matchers include text for helper methods
|
||||
# defined using `chain`, e.g.:
|
||||
# be_bigger_than(2).and_smaller_than(4).description
|
||||
# # => "be bigger than 2 and smaller than 4"
|
||||
# ...rather than:
|
||||
# # => "be bigger than 2"
|
||||
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||
end
|
||||
|
||||
# rspec-mocks config goes here. You can use an alternate test double
|
||||
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
||||
config.mock_with :rspec do |mocks|
|
||||
# Prevents you from mocking or stubbing a method that does not exist on
|
||||
# a real object. This is generally recommended, and will default to
|
||||
# `true` in RSpec 4.
|
||||
mocks.verify_partial_doubles = true
|
||||
end
|
||||
|
||||
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
||||
# have no way to turn it off -- the option exists only for backwards
|
||||
# compatibility in RSpec 3). It causes shared context metadata to be
|
||||
# inherited by the metadata hash of host groups and examples, rather than
|
||||
# triggering implicit auto-inclusion in groups with matching metadata.
|
||||
config.shared_context_metadata_behavior = :apply_to_host_groups
|
||||
|
||||
# The settings below are suggested to provide a good initial experience
|
||||
# with RSpec, but feel free to customize to your heart's content.
|
||||
=begin
|
||||
# This allows you to limit a spec run to individual examples or groups
|
||||
# you care about by tagging them with `:focus` metadata. When nothing
|
||||
# is tagged with `:focus`, all examples get run. RSpec also provides
|
||||
# aliases for `it`, `describe`, and `context` that include `:focus`
|
||||
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
||||
config.filter_run_when_matching :focus
|
||||
|
||||
# Allows RSpec to persist some state between runs in order to support
|
||||
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
||||
# you configure your source control system to ignore this file.
|
||||
config.example_status_persistence_file_path = "spec/examples.txt"
|
||||
|
||||
# Limits the available syntax to the non-monkey patched syntax that is
|
||||
# recommended. For more details, see:
|
||||
# https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/
|
||||
config.disable_monkey_patching!
|
||||
|
||||
# Many RSpec users commonly either run the entire suite or an individual
|
||||
# file, and it's useful to allow more verbose output when running an
|
||||
# individual spec file.
|
||||
if config.files_to_run.one?
|
||||
# Use the documentation formatter for detailed output,
|
||||
# unless a formatter has already been configured
|
||||
# (e.g. via a command-line flag).
|
||||
config.default_formatter = "doc"
|
||||
end
|
||||
|
||||
# Print the 10 slowest examples and example groups at the
|
||||
# end of the spec run, to help surface which specs are running
|
||||
# particularly slow.
|
||||
config.profile_examples = 10
|
||||
|
||||
# Run specs in random order to surface order dependencies. If you find an
|
||||
# order dependency and want to debug it, you can fix the order by providing
|
||||
# the seed, which is printed after each run.
|
||||
# --seed 1234
|
||||
config.order = :random
|
||||
|
||||
# Seed global randomization in this process using the `--seed` CLI option.
|
||||
# Setting this allows you to use `--seed` to deterministically reproduce
|
||||
# test failures related to randomization by passing the same `--seed` value
|
||||
# as the one that triggered the failure.
|
||||
Kernel.srand config.seed
|
||||
=end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Home" do
|
||||
it "shows the placeholder landing page" do
|
||||
visit root_path
|
||||
|
||||
expect(page).to have_content("Car Tracker")
|
||||
expect(page).to have_content("Vehicle maintenance and fuel tracking will be added here.")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user