4
11
Fork
You've already forked tomato
1
Software that aids community-based organizations in redistributing resources. Rootable manages schedules, deliveries, donations, volunteers, and data for volunteer shifts. https://rootable.org/
  • HTML 34.5%
  • JavaScript 31.8%
  • Ruby 22.2%
  • CSS 10.6%
  • SCSS 0.6%
  • Other 0.2%
Edward Loveall c8fdad00c4
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Show monthly/task totals on tracker show page
`TrackerTotals` is similar to `RecentTaskLogTotals`. It sums and groups task log
totals by month and task, instead of `RecentTaskLogTotals` which does it by
month and food taxonomy.
For styling, I reused the sites table because it looks similar to the list-table
styles, but can work better with variable columns. Because we're using tasks as
the columns, these tables can get _large_. So I also wrapped the table in a
scrolling container. There's a nice side benefit that we get that we can make a
minimum column width (only if `table-layout: auto;`) so that they don't get
squished too much.
2026年07月08日 16:56:20 -04:00
.woodpecker Upgrade to ruby 4.0.5 2026年06月17日 09:32:11 -04:00
app Show monthly/task totals on tracker show page 2026年07月08日 16:56:20 -04:00
bin Instructions for adding demo data to acceptance 2026年05月04日 09:33:57 -04:00
config Show monthly/task totals on tracker show page 2026年07月08日 16:56:20 -04:00
db Show monthly/task totals on tracker show page 2026年07月08日 16:56:20 -04:00
docs Add some info about how to get raw server logs 2026年06月17日 09:24:58 -04:00
lib Trigger CI cleanup on every CI run 2026年04月14日 15:10:13 -04:00
log Raygun-zapped skeleton. 2020年09月10日 10:59:29 -07:00
public Update landing page 2026年05月20日 14:18:12 -04:00
spec Show monthly/task totals on tracker show page 2026年07月08日 16:56:20 -04:00
vendor/assets Raygun-zapped skeleton. 2020年09月10日 10:59:29 -07:00
.env.sample Fix URL host 2026年05月04日 10:37:01 -04:00
.eslintrc.json Prettier 2026年06月03日 10:12:55 -04:00
.gitignore Update README to match reality 2026年02月12日 09:58:31 -05:00
.gitmodules Remove gitlab references 2025年10月16日 08:33:18 -04:00
.node-version Upgrade to webpacker 6.0.0.rc6 2025年07月23日 14:13:19 -04:00
.nvmrc Specify node version 2020年09月11日 09:14:59 -07:00
.prettierignore Prettier 2026年06月03日 10:12:55 -04:00
.rspec Always use format describe 2025年08月05日 22:35:30 +02:00
.rubocop.yml Prettier 2026年06月03日 10:12:55 -04:00
.rubocop_todo.yml Prettier 2026年06月03日 10:12:55 -04:00
.ruby-gemset Raygun-zapped skeleton. 2020年09月10日 10:59:29 -07:00
.ruby-version Upgrade to ruby 4.0.5 2026年06月17日 09:32:11 -04:00
.standard.yml Install SolidQueue 2026年04月16日 13:37:37 -04:00
babel.config.js Prettier 2026年06月03日 10:12:55 -04:00
Brewfile Clean up old heroku references 2026年03月31日 11:47:38 -04:00
Caddyfile Update README to match reality 2026年02月12日 09:58:31 -05:00
config.ru standardrb --fix 2025年06月26日 19:45:08 -04:00
docker-compose.yml Upgrade to ruby 3.4 2026年02月11日 11:39:35 -05:00
esbuild.config.mjs Migrate inline scripts to assets & rework shift event form logic 2025年12月08日 10:49:39 -05:00
Gemfile Add rails-i18n and tweak settings 2026年06月11日 11:57:31 -04:00
Gemfile.lock Upgrade dotenv/dotenv-rails to next major version 2026年06月17日 09:32:11 -04:00
Guardfile standardrb --fix 2025年06月26日 19:45:08 -04:00
LICENSE Switch to AGPLv3 2026年02月12日 09:58:31 -05:00
NEWS.md Prep release 2026年06月30日 2026年06月30日 14:50:46 -04:00
package.json Upgrade to react 19 2026年06月15日 10:34:32 -04:00
Procfile.dev Install SolidQueue 2026年04月16日 13:37:37 -04:00
Rakefile Raygun-zapped skeleton. 2020年09月10日 10:59:29 -07:00
README.md Fix URL host 2026年05月04日 10:37:01 -04:00
setupJest.js Upgrade react-router to v7 2026年06月11日 13:48:00 -04:00
yarn.lock Update the language after the user submits their profile 2026年06月16日 14:27:32 -04:00

Rootable

Licensing

This repository uses the AGPLv3 license. See the LICENSE file for full text. If you would like to purchase a license for commercial, closed source use, contact software@foodrescuealliance.org.

Development

First Time Setup

Requirements

To run the specs or fire up the server, be sure you have these installed (and running):

bin/setup

Run ./bin/setup to install missing gems and prepare the database.

Note, rails db:sample_data (run as part of setup) loads a small set of data for development. Check out db/sample_data.rb for details.

Then run seeds:

bundle exec rake db:seed

Running the Application Locally

The easiest way to run the app is using yarn start. This starts all the processes defined in Procfile.dev, including the Caddy reverse proxy, Rails server, and the asset recompilation.

yarn start

The app will then be accessible at https://rootable.localhost.

If you'd like to setup your own custom Overmind config, create Procfile.local (ignored by git) and set it up your way. Then, tell Overmind you'd like to use this by default in a .overmind.env file:

OVERMIND_PROCFILE=Procfile.local

Run overmind start instead of yarn start to run the processes.

Building assets separately

We use esbuild to build assets locally.

In production environments, the app builds the assets with rake assets:precompile, which eventually calls esbuild via node esbuild.config.mjs.

In local environments, you can use yarn build to build once, or yarn build --watch to have assets rebuilt automatically when files change.

.env

The bin/setup script will create a .env file that defines settings for your local environment. Do not check this into source control. Refer to the environment variables section below for what can be specified in .env.

Sending mail

In production we use Sendgrid. In development we use mailcatcher.

gem install mailcatcher
mailcatcher
open http://localhost:1080/

Learn more at mailcatcher. By recommendation of the library, we don't add this to our gemfile.

Running the Specs

To run all Ruby specs:

rspec

To run all the javascript specs:

yarn test

Deploying

See the Hatchbox documentation.

Conventions

Git

  • Create feature branches off of main with your initials and a brief description; e.g. xy-add-magic-to-orgs.
  • Multiple, meaningful commits with great commit messages are encouraged. Don't feel like you must squash every PR down into one.
  • Push to Codeberg and open a pull request. Seek someone else to review your code.
  • Feel free to commit a lot as you go and rebase against main to tidy up after a PR is approved.

Code Style

Standard is configured to enforce the style guide for this project.

Additional/Optional Development Details

Styling

Note: Material has moved on quite a bit since this app was created. To avoid the effort of keeping up with an external design system, this app is transitioning to a lightweight, in-house design system that is mostly (or hopefully fully) CSS with no additional frameworks. The notes below are historical, but still useful as we transition.

Two different styling systems are used within the application. The Admin experience is rendered from the server and utilizes Material Design for styling its components. The Volunteer experience is rendered from the client and utilizes Material UI for styling its components.

Server-Side Rendered Pages

Server-side rendered pages uses the simplest approach towards styling its components using Material Design. Theming is applied by overriding CSS custom properties. See _layout.scss for where we've set theming CSS custom properties.

Client-Side Rendered React

Note: We'd also like to mostly transition to sever-rendered pages everywhere, and only use dynamic pages in small quantities. They're easier to maintain, reason about, test, and they avoid the effort of keeping up with frontend framework changes. There is still plenty of react in the app so this will take a while and these notes are useful for developing in the meantime.

React State Management With Contexts

The react application uses react contexts to hold application state. We prefer to create smaller bite sized contexts scoped to particular slices of the application, and reserve the global application context to hold state that is truly global - such as the currently logged in user or theme settings. For each slice we define a dispatch and state context, which the useReducer hook produces values for. In this way we maintain the usual action -> reducer -> render data flow as seen in typical react/redux applications:

 _____
 | |
 | API |
 |_____|
 ^
 |
 _________ _____|______ __________ _________
 | | | | | | | |
 | actions |---->| middleware |---->| reducers |---->| context |
 |_________| |____________| |__________| |_________|
 ^ |
 | |
 | __________________ |
 | | | |
 --------------| react components |<---------------
 |__________________|

Spinning Up A Context

Creating a new context can be simplified using the useProvider custom hook. This hook takes a state and dispatch context, a reducer, and an initial state. It returns a Provider component and a connect function. Here is an example of setting up a context using the useProvider custom hook:

// domains/my-domain/context.js
import * as React from 'react';
import { defaultState, reducer } from './reducer';
import useProvider from '../../../reduxlike-contexts/useProvider';
const MyDomainStateContext = React.createContext();
const MyDomainDispatchContext = React.createContext();
const [MyDomainProvider, connectMyDomain] = useProvider(
 MyDomainStateContext,
 MyDomainDispatchContext,
 reducer,
 defaultState,
);
export { MyDomainProvider, connectMyDomain };

In order to connect to the state in this context, wrap a component tree in the resulting provider:

// domains/my-domain/AComponent.jsx
import { MyDomainProvider } from './context'
...
<MyDomainProvider>
 <ChildComponent />
</MyDomainProvider>
...

You may then connect any child of this component tree:

// domains/my-domain/ChildComponent.jsx
import { connectMyDomain } from './context'
...
const ChildComponent = ({ dispatch, height, width }) => { ... }
export default connectMyDomain(ChildComponent)
...

This will inject the dispatch function and each top level value in the context's state as props.

In this example, if the context's state looks like {height: 5, width: 10}, then connectMyDomain provides the dispatch function as well as height and width as props to the wrapped component.

Actions and Async Actions

Actions work in the expected way, defining action constants (const SET_HEIGHT = 'SET_HEIGHT')) and action creator functions (const setHeight = (height) => ({ type: SET_HEIGHT, data: height })).

We also use a thunks-style middleware which allows for async actions in the form of second order functions, e.g.:

const fetchShape = (arguments) => async (dispatch) => {
 dispatch(setLoading(true));
 const result = await asyncMethod(arguments);
 dispatch(setShape(result));
}

Any such second order function should work as long as the resulting function takes a dispatch as the only argument.

Code Coverage (local)

Coverage for the ruby specs:

$ COVERAGE=true rspec

Using chromedriver

The chromedriver version used in this project is maintained by the selenium-webdriver gem. selenium-webdriver handles downloading and installing chromedriver automatically for you and exists outside of any chromedriver previously installed on the machine.

Headed vs headless Chrome

System specs marked with js: true run using headless Chrome by default. When writing or troubleshooting specs, you may want to run the normal (i.e. "headed") version of Chrome so you can see what is being rendered and use the Chrome developer tools.

To do so, specify HEADLESS=false in your environment when running the specs. For example:

$ HEADLESS=false bin/rspec spec/system

Continuous Integration with TeamCity

This uses TeamCity for CI. The configuration is configured in the private teamcity repository.

Landing Page

The landing page is a static site built-in a different repo that we have as a submodule here. When the landing page repo is updated, we need to be sure to update it here. Do that with:

$ git submodule init
$ git submodule update
$ git submodule foreach git pull origin main
$ rake landing_page:update

If you use an IDE to git "add" it might not see the submodule update. Be sure to use the command git commit -a to ensure it finds it.

Importing Organizations from Food Rescue Robot

The rake task robot:import_region was created to help organizations transition from Food Rescue Robot to Rootable. This task copies over a Region's users, sites, and schedules to a Rootable instance.

To import an organization, first load a database dump from Food Rescue Robot into a Postgres instance that Rootable can connect to. Make note of the DB's connection string, you'll set this as the ROBOT_DATABASE_URL environment variable when running the import.

Next, determine the Region ID of the Organization you are going to import. One way to do this is by fetching the region in a Rails console like this:

 $ rails c
 > Robot::Region.where("name like '%Boulder%'")
 => [#<Robot::Region id: 1, ...>, ...]

Next, choose a cutover date: this is the date after which the organization will begin using Rootable. Shifts will be scheduled starting one day after this date. You'll pass this in as CUTOVER_DATE in the yyyy-mm-dd format.

Finally, run the import:

rails robot:import_region CUTOVER_DATE=2023年01月01日 REGION_ID=1

This will take a moment, but you should see some notes on what's imported as the data is transferred.

Limitations

Due to time constraints, there are some limitations that should be noted:

  • The import is not idempotent. Meaning, a new region will be created each time robot:import_region is run.
  • Pickup/dropoff logs from volunteers will not be created. Only volunteers, locations, and shifts are imported.
  • Any existing Rootable users (matched by email address) will be removed from their current organization and added to the newly created organization. When an existing user is found, their organization membership and role will be updated, but additional things like name, timezone, and phone number, will not be updated. You will see a note when existing users are encountered during the import.
  • Any Food Rescue Robot admins will be imported as volunteers if they are currently assigned shifts because Rootable used to not allow assigning admins to shifts.

Server Environments

Hosting

Acceptance and Production are hosted on Hatchbox under the Rootable account. You must be added to the account to access.

Environment Variables

Several common features and operational parameters can be set using environment variables.

Required for deployment

  • DATABASE_URL - URL of the PostgreSQL database; e.g. postgres://user:password@host:port/database
  • NODE_ENV - Set to production for all deployment environments
  • RACK_ENV - Set to production for all deployment environments
  • RAILS_ENV - Set to production for all deployment environments
  • SECRET_KEY_BASE - Secret key base for verifying signed cookies. Should be 30+ random characters and secret!
  • SMTP_HOST - SMTP server address
  • SMTP_PORT - SMTP server port
  • SMTP_AUTHENTICATION - SMTP authentication type, "plain" or "login"
  • SMTP_USERNAME - Username for SMTP authentication
  • SMTP_PASSWORD - Password for SMTP authentication
  • SMTP_SENDER_ADDRESS - Address from which emails will be sent; e.g. Name <user@domain.com>
  • AWS_BUCKET_NAME - Backblaze B2 bucket name for storing attachments
  • AWS_ACCESS_KEY_ID - Backblaze B2 bucket key ID for storing attachments
  • AWS_SECRET_ACCESS_KEY - Backblaze B2 bucket secret for storing attachments
  • CANONICAL_HOSTNAME - Canonical hostname for this application. Other incoming requests will be redirected to this hostname. Also used by mailers to generate full URLs; e.g. rootable.org

Optional

  • ASSET_HOST - Load assets from this host (e.g. CDN) (default: none).
  • BASIC_AUTH_PASSWORD - Enable basic auth with this password.
  • BASIC_AUTH_USER - Set a basic auth username (not required, password enables basic auth).
  • CONTACT_EMAIL_ADDRESS - Address for users to reach out about administrative details. This is meant to reach the party responsible for running the app, not individual organizations.
  • DB_POOL - Number of DB connections per pool (i.e. per worker) (default: RAILS_MAX_THREADS or 5).
  • FORCE_SSL - Require SSL for all requests, redirecting if necessary (default: false).
  • HATCHBOX_ACCEPTANCE_DEPLOY_KEY - key to deploy to hatchbox acceptance (local only)
  • HATCHBOX_PRODUCTION_DEPLOY_KEY - key to deploy to hatchbox production (local only)
  • PORT - Port to listen on (default: 3000) (local only)
  • RACK_TIMEOUT_SERVICE_TIMEOUT - Terminate requests that take longer than this time (default: 15s).
  • RAILS_LOG_TO_STDOUT - Log to standard out, good for Hatchbox (default: false).
  • RAILS_MAX_THREADS - Threads per worker (default: 5).
  • RAILS_MIN_THREADS - Threads per worker (default: 5).
  • RAILS_SERVE_STATIC_FILES - Serve static assets (default: false).
  • WEB_CONCURRENCY - Number of puma workers to spawn (default: 1).