- Right To Know
Right To Know lets you make and browse Freedom of Information (FOI) requests in Australia. It is powered by the open source FOI request platform Alaveteli.
This repository contains the theme package for Alaveteli for the Australian deployment. If you find a problem with Right to Know, please report it to this repository's issue tracker.
At present, we use a fork of Alaveteli which contains minor changes to the core to support us. Our plan is to transition to using upstream as soon as possible.
To find out which version of Alaveteli a site is currently running, ask the site rather than this README, which would only go stale:
- Production: https://www.righttoknow.org.au/version.json
- Staging: https://www-staging.righttoknow.org.au/version.json
If there is a fix or enhancement that is not specific to Right to Know/Australia changes should be submitted to the upstream Alaveteli repository via a pull request. In the vast majority of cases we will not deploy a fix until it's been accepted upstream. This ensures we're all using the same code as much as possible.
The one exception for now is announcement styles and rich text, which this theme carries until upstream accepts pull request 9644. It adds two migrations, which run with the host's (rake db:migrate or cap deploy:migrate). See lib/announcement_patches.rb for what to delete when upstream releases it.
However if you'd like to adjust the look and feel of Right To Know, or to update copy like that found on the help pages, this is the place to make those changes.
This repository is an Alaveteli theme — it runs inside
our fork of Alaveteli. You don't
need to check anything else out, though: the development environment below
clones Alaveteli for you (the staging branch by default) and wires this
theme into it, along with a working development configuration.
The stack matches Alaveteli's own Docker environment: Rails app, Sidekiq, PostgreSQL, Redis, and MailHog to catch outgoing email (at http://localhost:1080).
Works with VS Code (needs Docker installed) or GitHub Codespaces (needs nothing installed at all).
-
Open this repository in VS Code and choose Reopen in Container when prompted (or create a Codespace on GitHub).
-
Wait for setup to finish — the first run clones Alaveteli, installs gems, and migrates and seeds the databases, so it takes a while.
-
Start the server from the integrated terminal:
cd /alaveteli && bin/rails server -b 0.0.0.0
-
Open http://localhost:3000. Log in as the development admin with the username and password from
config/general-righttoknow.yml.example(adminxxxx/passwordx).
Inside the container, Alaveteli lives at /alaveteli and this theme is
mounted at /alaveteli-themes/righttoknow (your workspace folder). Theme
changes take effect on reload; changes to Ruby files under lib/ need a
server restart.
The same stack without VS Code — just Docker and make:
make setup # first run: build, clone Alaveteli, migrate, seed (takes a while)
make server # start the site at http://localhost:3000Other useful targets (make help lists them all):
make console # Rails console
make test # run this theme's specs
make seed # load realistic AU test data (see "Seeding test data")
make shell # shell in the app container
make reset # destroy containers and data, set up from scratchTo develop against a different Alaveteli branch or fork, set
ALAVETELI_BRANCH / ALAVETELI_REPO before the first make setup. If ports
3000 or 1080 are already in use on your machine, set RAILS_PORT /
MAILHOG_PORT.
If you can't use containers, follow the Alaveteli manual installation docs with our Alaveteli fork, using the layout Alaveteli's theme tooling expects:
- Check out the fork, and create a sibling directory called
alaveteli-themeswith this repository inside it (alaveteli/andalaveteli-themes/righttoknow/side by side). - Copy this repository's
config/general-righttoknow.yml.exampletoalaveteli/config/general-righttoknow.yml. - From the
alavetelidirectory runbundle exec script/switch-theme.rb righttoknow.
script/seed_test_data.rb fills a development or
test environment with a realistic subset of production data so authority
listings, jurisdiction logic and request states behave like the real site.
It creates:
- A handful of real authorities per jurisdiction tag, taken from
production's public
all-authorities.csvexport (public information only — name, tags, URL slug — no request PII). Every seeded authority is given a dummy@example.comrequest email so the environment can never contact a real authority. - Dummy requests per authority spread across a range of statuses, with a
subset of authorities carrying 3+
requester_only(prominence) requests. - A browse-by-category taxonomy synthesised from the jurisdiction tags on the imported authorities. This is not a copy of production's own category structure — production does not publish its category definitions.
The script refuses to run in production.
Run it against the Alaveteli app (not this theme repo) with rails runner.
In the development environment above it's one command:
make seedor manually (from the alaveteli directory):
bundle exec rails runner \
../alaveteli-themes/righttoknow/script/seed_test_data.rbAuthorities and the browse-by-category page work immediately. Search and the
request listings are Xapian-backed and will not show seeded data until the
index is updated — make seed does this for you; otherwise either re-run with
SEED_REBUILD_INDEX=1, or run:
bundle exec rake xapian:destroy_and_rebuild_index \
models="PublicBody User InfoRequestEvent"Optional environment variables:
| Variable | Purpose |
|---|---|
SEED_CSV_URL |
Override the production authorities CSV URL. |
SEED_CSV_PATH |
Read authorities from a local CSV instead of fetching (handy offline; expects the all-authorities.csv format). |
SEED_BODIES_PER_TAG |
Number of authorities per jurisdiction tag (default 5). |
SEED_REBUILD_INDEX |
Set to 1 to update the Xapian index at the end so seeded data shows up in search and request listings. |
The script is idempotent: re-running it reuses existing authorities and categories and won't stack additional seeded requests onto authorities that already have them.
Stripe coupons can only be limited to a product, not to an individual
price. Because the Pro monthly and annual plans are two prices under one
product, Stripe's own applies_to restriction cannot stop a coupon from
discounting both. Some coupons are meant for a single plan (for example, a
monthly-only referral coupon).
The theme enforces a per-interval restriction itself, driven by coupon metadata so the rule lives with the coupon in Stripe (no code change or deploy needed to add or adjust restricted coupons):
- Add a metadata key
intervalto the coupon in the Stripe dashboard, set to the billing interval the coupon is allowed on:day,week,monthoryear. A monthly-only coupon takesinterval=month. - A coupon without an
intervalmetadata key is unrestricted and applies to any plan, exactly as before.
With interval set, lib/controller_patches.rb
blocks the coupon at checkout on a non-matching plan (before any subscription is
created) and the live plan-page price preview refuses it too, so a discount is
never advertised that checkout would then reject.
For extra defence-in-depth you can also set the coupon's native applies_to
products to the Pro product; this stops it discounting any other product, though
it still can't separate monthly from annual.
The "Do you have a coupon code?" field on the plan page accepts a Stripe promotion code as well as a coupon id. A promotion code is a customer-facing string pointing at a coupon, and it carries restrictions a coupon cannot express: a redemption cap for that code alone, first-time subscribers only, and an expiry independent of the coupon's. Many codes can share one coupon, so a campaign can be tracked and retired without touching the discount itself.
To add one, open the coupon in the Stripe dashboard and create a promotion code against it.
Two things differ from coupons:
- Promotion codes are not namespaced. Coupon ids are prefixed with
STRIPE_NAMESPACE(a typedFOOlooks up the couponRTK-FOO), but a promotion code is matched exactly as typed. Create the code as the string you want people to type. - Coupons win a collision. A typed code is looked up as a coupon first, then
as a promotion code, so a coupon
RTK-FOOtakes precedence over a promotion codeFOO. Avoid using the same string for both.
Only active promotion codes resolve. Stripe deactivates a code when it expires or hits its redemption cap, and a deactivated code reads as invalid rather than expired.
Who enforces what:
| Restriction | Enforced by |
|---|---|
max_redemptions, expires_at, per-customer |
Stripe, on redemption |
first_time_transaction |
Stripe, plus a check here before any charge |
minimum_amount |
Stripe, plus a check here before any charge |
interval metadata (see above) |
This theme |
A minimum_amount is compared against the plan price before tax and before
the discount, so a $10 monthly plan meets a minimum of 1000 but not 1050. A
minimum set only in another currency is treated as unmeetable.
The interval and humanized_terms metadata keys are read from the underlying
coupon. You can override either on the promotion code's own metadata, so one
coupon can back several codes that describe themselves differently.
If you want to modify the customised look and feel of Right To Know then you should edit this repository however if it's something more general you probably want to edit the upstream core Alaveteli software.
To contribute an enhancement or a fix to this theme:
- Fork the project on GitHub.
- Make a topic branch from the
stagingbranch. - Make your changes and test.
- Commit the changes without making changes to any files that aren't related to your enhancement or fix.
- Send a pull request against the
stagingbranch.
The application is deployed using Capistrano 3. Deployment is run from this repository against the alaveteli codebase.
Releases to production happen as a staging→production release pull request.
As part of each release PR, add a section to CHANGELOG.md
covering everything merged to staging since the previous release.
Capistrano looks up the EC2 deploy targets dynamically by their Application and Stage tags
and tunnels SSH through AWS SSM Session Manager
rather than connecting to a public hostname, so you need:
- The AWS CLI and the Session Manager plugin installed
- An AWS profile named
oafwith permission to describe EC2 instances and start SSM sessions.config/deploy.rbsetsAWS_PROFILEtooafunless you have already set it, so instance lookup and the SSM tunnel always use the same account; setAWS_PROFILEyourself if your profile is named something else - An SSH key for the
deployuser (SSH still runs as normal inside the SSM tunnel). If your key isn't picked up by default, add aHost i-*entry withIdentityFileto~/.ssh/config - Gems installed:
bundle install --with deployment - The server must have
shared/rbenv-version,shared/general.yml, and all other shared files in place (managed by the infrastructure repo)
To check which instances a stage will deploy to:
bundle exec cap staging aws:ec2:instancesDeploy to staging:
bundle exec cap staging deployDeploy to production:
bundle exec cap production deployRun database migrations only (maintenance page shown automatically):
bundle exec cap staging deploy:migrate
bundle exec cap production deploy:migrateRestart the application without deploying:
bundle exec cap staging deploy:restart
bundle exec cap production deploy:restartRebuild the Xapian search index:
bundle exec cap staging xapian:destroy_and_rebuild_index
bundle exec cap production xapian:destroy_and_rebuild_indexOne-off jobs that act on people's accounts. Each is dry unless you pass DRYRUN=0, and each
prints account ids and dates rather than email addresses, so the output is safe to paste into an
issue as a record of what ran.
Destroy never-confirmed accounts that have no content and are over two years old (#1096):
# List what would go, and check the count against production before acting
bundle exec cap production accounts:destroy_never_confirmed
# Start small, check Sentry, then run the rest
bundle exec cap production accounts:destroy_never_confirmed DRYRUN=0 LIMIT=10
bundle exec cap production accounts:destroy_never_confirmed DRYRUN=0These accounts never confirmed their email address, so they cannot be warned first and are handled
separately from the dormant-account notice. What "never-confirmed" and "dormant" mean, and why the
order matters, is in AGENTS.md under "Key domain knowledge" and in
ADR-0003.
Tell the remaining dormant accounts they will be removed unless they sign in (#1095):
# List who would be notified, and the removal date the email would quote
bundle exec cap production accounts:send_dormant_notices
# Send one tranche, then watch the bounce count before sending the next
bundle exec cap production accounts:send_dormant_notices DRYRUN=0 LIMIT=20
bundle exec cap production accounts:send_dormant_notices DRYRUN=0Do not send these live until bounce recording works (#1094). Without it nothing suppresses repeat sends to addresses that have permanently failed, and there is no way to judge the send. Right to Know's sending reputation is what gets FOI requests delivered to authorities.
Each notice quotes a removal date of the run date plus 60 days, and each account notified is tagged
dormant_account_notice:<date> so a later run never mails the same person twice. The host's
users:destroy_unused cron must not be enabled before the latest date any tranche was told. The
run prints that date so it can be recorded on the issue.
Either job can be run from a shell on the server, with the same environment variables:
cd /srv/www/production/current
lib/themes/righttoknow/script/destroy-never-confirmed-accounts
lib/themes/righttoknow/script/send-dormant-account-noticesAfter the infrastructure repo has provisioned the host, the shared path needs the config files in place before the first deploy. The deploy itself auto-creates any missing shared directories (cache, logs, storage, xapian indexes, vendored bundle, etc.) on each run via deploy:check_shared.
One-time bootstrap per server:
- Ensure
<deploy_to>/shared/exists (ssh deploy@<host> 'mkdir -p <deploy_to>/shared'). - Place the shared config files at the top level of
shared/(basename only, not undershared/config/). The expected files — drawn fromSHARED_FILESinalaveteli/config/general-righttoknow.yml— are:general.ymldatabase.ymlsidekiq.ymlstorage.ymluser_spam_scorer.ymlrails_env.rbnewrelic.ymlfoi-live-creation.pngfoi-user-use.pngrbenv-version
- Run the first deploy:
bundle exec cap <stage> deploy.
deploy:check_shared runs at the start of every deploy and fails fast if any required file is missing.
GET /whatismyip reports the IP Rails sees for the request, to aid in checking cloudflare proxying
with a one-line curl rather than a real sign-in or a log dig. It returns the IP, or the IP plus
FAIL if that IP falls within Cloudflare's own published ranges - a sign the trust boundary isn't rewriting
it to the real visitor IP. If Cloudflare's published ranges can't be fetched or look truncated, it
returns the IP plus UNABLE TO CHECK rather than guessing.
It should report the same value as https://whatismyip.akamai.com/
Off by default; set PROVIDE_WHATISMYIP: true in general.yml to turn it on.
curl https://www.righttoknow.org.au/whatismyip
curl https://www-staging.righttoknow.org.au/whatismyipNot everything that the government considers to be a distinct agency is an entity that people actually want to make requests to. It is very much a subjective call, but always try and make the site most useful to the people who use it. If there is a lot of unnecessary sub-division authorities for example, requests will be more difficult to find in the site. Try to avoid this. For example, the IT division or the head office of an agency are probably just part of the agency itself for the purpose of information requests.
If a sub-department agency has a distinct office, it’s own website and information request email address, and does stuff that people would want to make requests about, it should probably be a distinct authority in Right To Know.
If in doubt, ask the team.
For state authorities often the name of the state appears in the name of the authority.
On the Art Gallery of NSW website they refer to their own name in three different forms: Art Gallery NSW, Art Gallery of New South Wales, and Art Gallery of NSW. We're currently using Art Gallery of NSW because it is commonly used, succinct, and searchable. Therefore if the name of the state is in the name of the authority, use the form they use or the form in most common use.
When you need to choose, use real acronyms (NSW, ACT, WA, SA, NT) but not contractions (use Victoria, Tasmania, and Queensland, not VIC, TAS, or QLD).
When collecting the email address that requests to the authority are sent to, we need to find the best address to deal with them directly. A specific address for the authority isn’t always available, especially when they exist within a bigger department. This is the order of preference for the authority’s request email:
- Specific FOI address for sub-department agency (e.g. <foi@special_agency.gov.au>)
- Specific address for sub-department agency at department (e.g. special_agency@department.gov.au)
- Specific FOI address for department (e.g. foi@department.gov.au)
- Generic address for department (e.g. info@department.gov.au)
- Address for a specific person at the agency (e.g. jane.zhang@dept.gov.au)
A person’s address is the absolute last resort.
You can add public notes about the authority. The notes are displayed on the authority’s page, as an excerpt in lists of authorities, and as a highlighted notice when someone makes a request to the authority. They are covered in the site search.
The note could include important information about requesting from this department, key terms people might use when searching for the agency, and a basic description of the authority. For example:
HomeStart was created by the South Australian Government in 1989, as a response to high interest rates and a lack of affordable home loan finance options. HomeStart was established as a statutory corporation under the Housing and Urban Development (Administrative Arrangements) Act 1995 and reports to the Minister for Housing and Urban Development.
You can add abbreviated version of the authority’s name as it’s short name. These are really useful because the common name for an agency might not be it’s full formal name. For example, people commonly search “ABS” when looking for the “Australian Bureau of Statistics”.
It’s very important to only add acronyms or abbreviations that people really do use. These are displayed on authority pages and lists of authorities, and having lots of irrelevant short names adds unnecessary noise to the page.
Authorities that are defunct aren’t actually removed from Right To Know.
Add the defunct tag to the authority and it will no longer be available for requests.
People can still find requests to the authority in search results and see all
the requests that were made to them while they were active.
A unique aspect of Right To Know compared to other Alaveteli installations is that we're in the process of supporting 9 different jurisdictions - Federal and all the states and territories.
We have customisations in this theme to adjust the length of time authorities have to respond to a request and the law names depending on what jurisdiction an authority is covered by. These customisations rely on the use of Alaveteli's tags to work out what jurisdiction an authority is covered by.
The table below show what tag you need to use for each jurisdiction. Don't forget to also add the appropriate category tag, described in the section below, for the authority you're adding.
| Jurisdiction | Tag |
|---|---|
| Federal | federal |
| ACT | ACT |
| NSW | NSW |
| NT | NT |
| QLD | QLD |
| SA | SA |
| TAS | TAS |
| VIC | VIC |
| WA | WA |
Public authority categories are configured in the Alaveteli admin interface. This how we want Right To Know's categories organised:
| Title | Description | Tag |
|---|---|---|
| Agriculture | part of the Agriculture portfolio | agriculture |
| Attorney-General | part of the Attorney-General portfolio | attorney_general |
| Communications | part of the Communications portfolio | communications |
| Defence | part of the Defence portfolio | defence |
| Education and Training | part of the Education and Training portfolio | education_and_training |
| Employment | part of the Employment portfolio | employment |
| Environment | part of the Environment portfolio | environment |
| Finance | part of the Finance portfolio | finance |
| Foreign Affairs and Trade | part of the Foreign Affairs and Trade portfolio | foreign_affairs_and_trade |
| Health | part of the Health portfolio | health |
| Immigration & Border Protection | part of the Immigration & Border Protection portfolio | immigration_and_border_protection |
| Industry and Science | part of the Industry and Science portfolio | industry_and_science |
| Infrastructure and Regional Development | part of the Infrastructure and Regional Development portfolio | infrastructure_and_regional_development |
| Prime Minister | part of the Prime Minister portfolio | prime_minister |
| Social Services | part of the Social Services portfolio | social_services |
| Treasury | part of the Treasury portfolio | treasury |
| Veterans' Affairs | part of the Veterans' Affairs portfolio | veterans_affairs |
| All Federal authorities | a Federal authority | federal |
| Title | Description | Tag |
|---|---|---|
| ACT | an ACT authority | ACT_state |
| New South Wales | a NSW authority | NSW_state |
| Northern Territory | a Northern Territory authority | NT_state |
| Queensland | a Queensland authority | QLD_state |
| South Australia | a South Australian authority | SA_state |
| Tasmania | a Tasmanian authority | TAS_state |
| Victoria | a Victorian authority | VIC_state |
| Western Australia | a Western Australian authority | WA_state |
When adding authorities for jurisdictions we don't yet cover we need to:
- Update help and other text:
- Upload the new authorities (with the correct tags, see above)
- Add categories (see above)
This project is tested with BrowserStack
