Showing preview only (338K chars total). Download the full file or copy to clipboard to get everything.
Repository: discourse/discourse_api
Branch: main
Commit: 6e5e71b692bb
Files: 139
Total size: 306.0 KB
Directory structure:
gitextract_ygzwukcj/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .rubocop.yml
├── .streerc
├── CHANGELOG.md
├── Gemfile
├── Guardfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── config_example.yml
├── discourse_api.gemspec
├── examples/
│ ├── backups.rb
│ ├── badges.rb
│ ├── bookmark_topic.rb
│ ├── category.rb
│ ├── change_topic_status.rb
│ ├── create_private_message.rb
│ ├── create_topic.rb
│ ├── create_update_category.rb
│ ├── create_user.rb
│ ├── dashboard.rb
│ ├── disposable_invite_tokens.rb
│ ├── example.rb
│ ├── group_set_user_notification_level.rb
│ ├── groups.rb
│ ├── invite_users.rb
│ ├── manage_api_keys.rb
│ ├── notifications.rb
│ ├── polls.rb
│ ├── post_action.rb
│ ├── search.rb
│ ├── sent_private_messages.rb
│ ├── sso.rb
│ ├── topic_lists.rb
│ ├── update_user.rb
│ └── upload_file.rb
├── lib/
│ ├── discourse_api/
│ │ ├── api/
│ │ │ ├── api_key.rb
│ │ │ ├── backups.rb
│ │ │ ├── badges.rb
│ │ │ ├── categories.rb
│ │ │ ├── dashboard.rb
│ │ │ ├── email.rb
│ │ │ ├── groups.rb
│ │ │ ├── invite.rb
│ │ │ ├── notifications.rb
│ │ │ ├── params.rb
│ │ │ ├── polls.rb
│ │ │ ├── posts.rb
│ │ │ ├── private_messages.rb
│ │ │ ├── search.rb
│ │ │ ├── site_settings.rb
│ │ │ ├── sso.rb
│ │ │ ├── tags.rb
│ │ │ ├── topics.rb
│ │ │ ├── uploads.rb
│ │ │ ├── user_actions.rb
│ │ │ └── users.rb
│ │ ├── client.rb
│ │ ├── error.rb
│ │ ├── example_helper.rb
│ │ ├── single_sign_on.rb
│ │ └── version.rb
│ └── discourse_api.rb
└── spec/
├── discourse_api/
│ ├── api/
│ │ ├── api_key_spec.rb
│ │ ├── backups_spec.rb
│ │ ├── badges_spec.rb
│ │ ├── categories_spec.rb
│ │ ├── email_spec.rb
│ │ ├── groups_spec.rb
│ │ ├── invite_spec.rb
│ │ ├── notifications_spec.rb
│ │ ├── params_spec.rb
│ │ ├── polls_spec.rb
│ │ ├── posts_spec.rb
│ │ ├── private_messages_spec.rb
│ │ ├── search_spec.rb
│ │ ├── site_settings_spec.rb
│ │ ├── sso_spec.rb
│ │ ├── topics_spec.rb
│ │ ├── uploads_spec.rb
│ │ ├── user_actions_spec.rb
│ │ └── users_spec.rb
│ ├── client_spec.rb
│ └── single_sign_on_spec.rb
├── fixtures/
│ ├── admin_user.json
│ ├── api_key.json
│ ├── backups.json
│ ├── badges.json
│ ├── categories.json
│ ├── category_latest_topics.json
│ ├── category_topics.json
│ ├── create_topic_with_tags.json
│ ├── email_list_all.json
│ ├── email_settings.json
│ ├── group.json
│ ├── groups.json
│ ├── hot.json
│ ├── latest.json
│ ├── list_api_keys.json
│ ├── members_0.json
│ ├── members_1.json
│ ├── members_2.json
│ ├── new.json
│ ├── notification_success.json
│ ├── notifications.json
│ ├── polls_toggle_status.json
│ ├── polls_vote.json
│ ├── polls_voters.json
│ ├── post.json
│ ├── post_action_users.json
│ ├── posts_before.json
│ ├── posts_latest.json
│ ├── private_messages.json
│ ├── regenerate_api_key.json
│ ├── replies.json
│ ├── replies_and_topics.json
│ ├── retrieve_invite.json
│ ├── search.json
│ ├── top.json
│ ├── topic.json
│ ├── topic_invite_user.json
│ ├── topic_posts.json
│ ├── topics_created_by.json
│ ├── update_trust_level.json
│ ├── upload_avatar.json
│ ├── upload_file.json
│ ├── user.json
│ ├── user_activate_success.json
│ ├── user_badges.json
│ ├── user_create_success.json
│ ├── user_grant_admin.json
│ ├── user_grant_moderator.json
│ ├── user_list.json
│ ├── user_log_out_success.json
│ ├── user_update_avatar_success.json
│ ├── user_update_user.json
│ └── user_update_username.json
└── spec_helper.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
pull_request:
push:
branches:
- main
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true
- name: Rubocop
run: bundle exec rubocop
- name: syntax_tree
if: ${{ !cancelled() }}
run: |
set -E
bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby: ["2.7", "3.0", "3.1", "3.2", "3.3"]
steps:
- uses: actions/checkout@v4
- name: Setup ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Tests
run: bundle exec rake test
publish:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Release Gem
uses: discourse/publish-rubygems-action@v3
env:
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
GIT_EMAIL: team@discourse.org
GIT_NAME: discoursebot
================================================
FILE: .gitignore
================================================
Gemfile.lock
coverage
/config.yml
================================================
FILE: .rubocop.yml
================================================
inherit_gem:
rubocop-discourse: stree-compat.yml
Discourse/Plugins:
Enabled: false
================================================
FILE: .streerc
================================================
--print-width=100
--plugins=plugin/trailing_comma,plugin/disable_auto_ternary
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [2.1.0] - 2025-12-02
### Changed
- Add support for `external_id` in topic creation
### Added
- Add get topic url by `external_id`
## [2.0.1] - 2023-06-09
### Removed
- Invite admin method removed
## [2.0.0] - 2023-05-26
### Changed
- The package now requires ruby 2.7+
- The package now requires faraday 2.7+
## [1.1.0] - 2022-07-05
### Changed
- `DiscourseApi::SingleSignOn.parse` now raises `DiscourseApi::SingleSignOn::ParseError` (inherits from `RuntimeError` to preserve backward compatibility) instead of `RuntimeError` when there's a signature mismatch.
- `DiscourseApi::SingleSignOn.parse` now raises `DiscourseApi::SingleSignOn::MissingConfigError` (also inherits from `RuntimeError`) if `sso_secret` or `sso_url` are missing.
## [1.0.0] - 2022-05-01
### Changed
- The package now requires ruby 2.6+
## [0.48.1] - 2022-04-13
### Added
- New attributes for Discourse Connect (aka SSO)
## [0.48.0] - 2022-01-28
### Added
- `group_add_owners` method (#239)
- `group_remove_owners` method (#239)
- `anonymize` method (#241)
### Changed
- `DiscourseApi::Timeout` error now inherits from `DiscourseApi::Error` (#240)
- `DiscourseApi::SingleSignOn#groups` now returns an array of strings where each string is a group name, rather than an array with a single string that contains all the groups comma-concatenated (#243)
## [0.47.0] - 2021-07-19
### Added
- Update invite method
- Retrieve invite method
- Destroy all expired invites method
- Destroy invite method
- Resend all invites method
- Resend invite method
- Pass params to get notifications API
### Deprecated
- `invite_user_to_topic` has been deprecated, use `invite_to_topic` instead.
- `create_private_message` has been deprecated, use `create_pm` instead.
## [0.46.0] - 2021-04-12
### Added
- Allow bookmarking topics
- Add timeout to requests
- Add params to get_topic_posts
## [0.45.1] - 2021-03-11
### Added
- Fetch global top topics
- Allow setting topic notifications
- Return full category response
### Changed
- Use new search endpoint
## [0.45.0] - 2021-01-15
### Added
- Tag configuration in create_category/update_category
- Topic#change_owner
- Support passing approved to #create_user
### Changed
- API key methods use the latest endpoints
## [0.44.0] - 2020-11-13
### Fixed
- Updated `show_tag` method to use new route
### Removed
- Support for Ruby 2.3 and 2.4
## [0.43.1] - 2020-11-04
### Fixed
- Tagged version 0.43.0 got pushed without commit due to new master branch
protections in github. No, code changes here just making sure tags align with
commits.
## [0.43.0] - 2020-11-04
### Added
- Add pagination to list groups endpoint
### Deprecated
- `change_topic_status` has been deprecated, use `update_topic_status` instead.
## [0.42.0] - 2020-07-09
### Added
- Create topics with tags
## [0.41.0] - 2020-06-17
### Added
- Add basic auth support
### Fixed
- Fix SSO custom field prefixes
### Removed
- Obsolete api key endpoints
## [0.40.0] - 2020-05-07
### Fixed
- Add missing attributes to `sync_sso`
### Added
- Add delete category method
## [0.39.3] - 2020-04-30
### Fixed
- Add `reviewable_by_group_name` to categories
## [0.39.2] - 2020-04-30
### Fixed
- Add `members_visibility_level` to group
## [0.39.1] - 2020-03-27
### Fixed
- Ensure released gem version matches this commit
## [0.39.0] - 2020-03-27
### Added
- Get latest posts across topics via posts.json
- Allow more options parameters when creating a category
- Don't require topic slug when updating topic status
- Example files now read config.yml file when present for client settings
### Fixed
- Issue with `topic_posts` and frozen strings
- Fixed some topic and category methods
## [0.38.0] - 2019-10-18
### Added
- Allow setting locale in SingleSignOn
- Optional param to group members to include owners as well as members
## [0.37.0] - 2019-09-23
### Added
- user-badges endpoint for full badges list
- expanded list of allowed messages
- grant/revoke moderation
## [0.36.0] - 2019-07-18
### Added
- Added poll methods
### Fixed
- Updated create topic example
- Fixed capitalization for header auth keys
## [0.35.0] - 2019-05-15
### Added
- Added `custom_fields` param to create/update category
- Added `frozen_string_literal: true` to all the files
- Added rubocop and all the changes that went along with it
### Fixed
- Allow `api_username` to be changed for an initialized client
- Update many of the `/users` routes to use the `/u` route
### Changed
- Changed `update_trust_level` to follow consistent method param syntax where
you specify the id first followed by params
## [0.34.0] - 2019-04-02
### Added
- Header based authentication
### Removed
- Query param based authentication
## [0.33.0] - 2019-03-04
### Added
- Added a new method to update a users notification level in a category
## [0.32.0] - 2019-02-13
### Added
- Added a new method to update a users notification level in a group
## [0.31.0] - 2019-02-07
### Added
- Added `deactivate` method
- Added 201 and 204 as valid POST responses
## [0.30.0] - 2018-12-19
### Added
- Add params hash to `list_users`
## [0.29.0] - 2018-12-07
### Added
- Add `add_groups` and `remove_groups` to `sync_sso`
## [0.28.2] - 2018-11-26
### Fixed
- Updated arguments for suspending a user
## [0.28.1] - 2018-10-26
### Fixed
- Fixed non-URI chars in `check_username` method
## [0.28.0] - 2018-10-23
### Added
- Added `check_username` method
## [0.27.0] - 2018-09-14
### Added
- Added `site_settings_update` method
## [0.26.0] - 2018-09-10
### Added
- Added user `user_actions` endpoint so you can retrieve `user_replies` and
`user_topics_and_replies`
## [0.25.0] - 2018-08-15
### Added
- Added ability to rescue certain error classes and inspect the response object
## [0.24.0] - 2018-05-30
### Added
- Added support for custom `user_fields` when creating a user
## [0.23.1] - 2018-05-24
### Fixed
- Can now change `api_username` without creating a new client
## [0.23.0] - 2018-05-24
### Added
- Added `delete_user` method
## [0.22.0] - 2018-05-04
### Added
- Support for subfolder paths
## [0.21.0] - 2018-04-23
### Fixed
- Update GET groups api route
- Update PUT groups api route
## [0.20.0] - 2017-12-13
### Added
- Add base error class
### Fixed
- Update SSO
## [0.19.0] - 2017-11-22
### Added
- Added optional `create_post` params
## [0.18.0] - 2017-10-17
### Added
- Added `update_group` API call
### Fixed
- Fixed params for create groups endpoint
- Fixed invite token API endpoint
## [0.17.0] - 2017-06-29
### Added
- Add title to SSO sync
## [0.16.1] - 2017-06-23
### Fixed
- `user_sso` should use `user_id` instead of `username`
- `upload_file` should also include optional `user_id` param
## [0.16.0] - 2017-05-14
### Added
- added `upload_file`
### Removed
- removed `upload_post_image`
## [0.15.0] - 2017-04-12
### Added
- added the ability to create private messages
## [0.14.1] - 2016-12-20
### Fixed
- allow for rack 2.0+ versions so that it doesn't clash with rails.
## [0.14.0] - 2016-10-30
### Added
- improved error responses by adding `NotFoundError`, `UnprocessableEntity`, and `TooManyRequests`
- added `delete_post` method
## [0.13.0] - 2016-10-09
### Added
- added `update_category`
- added `upload_post_image`
## [0.12.0] - 2016-10-06
### Added
- add endpoint for `/admin/users/{id}/suspend`
- add endpoint for `/admin/users/{id}/unsuspend`
## [0.11.0] - 2016-09-03
### Fixed
- add destination folder to backup download
- `post_action_users`
### Added
- `change_topic_status`
- set username of topic on creation
## [0.10.1] - 2016-05-04
### Fixed
- raise an error if search is empty
- fix /category path to be just /c
- return errors for category_latest_topics if there are some
## [0.10.0] - 2016-04-19
### Added
- group_members: Allows you to retrieve more than 100 users with pagination (offset &
limit)
### Fixed
- Deprecation warning with SimpleCov
- updated rack dependency and added ruby 2.3 to travis config
## [0.9.1] - 2016-03-23
### Fixed
- topic and post like/flag need to use `:id`
## [0.9.0] - 2016-03-22
### Added
- can now like/flag topics and posts
## [0.8.1] - 2016-03-03
### Fixed
- enable use of discourse_api to make unauthenticated requests to discourse
endpoints like /categories and /topics
## [0.8.0] - 2016-02-28
### Added
- get stats from admin dashboard
- get only stat totals from admin dashboard
## [0.7.0] - 2015-12-09
### Added
- get user by external_id
## [0.6.2] - 2015-12-02
### Fixed
- `API::Params` will not work correctly when both optional and defaults are
specified
## [0.6.1] - 2015-11-28
### Fixed
- typo in topic_posts method
## [0.6.0] - 2015-11-27
### Added
- get posts in topic by an array of id's
## [0.5.1] - 2015-11-21
### Fixed
- remove puts statement
## [0.5.0] - 2015-11-21
### Added
- get latest category topics by page
## [0.4.0] - 2015-01-15
### Added
- generate an api key for a user
- revoke an api key for a user
- update user trust level
- grant user badge
## [0.3.6] - 2015-01-11
### Added
- list badges
- view email settings
- list emails sent
- list badges by user
- be able to specify SSL connection settings
- list api keys generated
- list backups created
## [0.3.5] - 2015-01-06
### Added
- Can now get a list of users by type: active, new, staff, etc.
- `client.category_latest_posts("category-slug")` endpoint
## [0.1.2] - 2014-05-11
- Release
================================================
FILE: Gemfile
================================================
# frozen_string_literal: true
source "https://rubygems.org"
# Specify your gem's dependencies in discourse_api.gemspec
gemspec
================================================
FILE: Guardfile
================================================
# frozen_string_literal: true
guard :rspec do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { 'spec' }
end
================================================
FILE: LICENSE.txt
================================================
Copyright (c) 2014 Civilized Discourse Construction Kit, Inc.
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# DiscourseApi
The Discourse API gem allows you to consume the Discourse API
## Installation
Add this line to your application's Gemfile:
gem 'discourse_api'
And then execute:
$ bundle
Or install it yourself as:
$ gem install discourse_api
## Usage
Over time this project intends to have a full Discourse API. At the moment there are only a
few endpoints available:
```ruby
client = DiscourseApi::Client.new("http://try.discourse.org")
client.api_key = "YOUR_API_KEY"
client.api_username = "YOUR_USERNAME"
client.ssl(...) #=> specify SSL connection settings if needed
# Topic endpoints
client.latest_topics #=> Gets a list of the latest topics
client.new_topics #=> Gets a list of new topics
client.topics_by("sam") #=> Gets a list of topics created by user "sam"
client.topic(57) #=> Gets the topic with id 57
# Search endpoint
client.search("sandbox") #=> Gets a list of topics that match "sandbox"
# Categories endpoint
client.categories #=> Gets a list of categories
client.category_latest_topics(category_slug: "lounge") #=> Gets a list of latest topics in a category
# SSO endpoint
client.sync_sso( #=> Synchronizes the SSO record
sso_secret: "discourse_sso_rocks",
name: "Test Name",
username: "test_name",
email: "name@example.com",
external_id: "2",
custom_fields: {
field_1: 'potato'
}
)
# Private messages
client.private_messages("test_user") #=> Gets a list of private messages received by "test_user"
client.sent_private_messages("test_user") #=> Gets a list of private messages sent by "test_user"
client.create_private_message( #=> Creates a private messages by api_username user
title: "Confidential: Hello World!",
raw: "This is the raw markdown for my private message",
target_usernames: "user1,user2"
)
```
You can handle some basic errors by rescuing from certain error classes and inspecting the response object passed to those errors:
```ruby
begin
client.create_group({ name: 'NO' })
rescue DiscourseApi::UnprocessableEntity => error
# `body` is something like `{ errors: ["Name must be at least 3 characters"] }`
# This outputs "Name must be at least 3 characters"
puts error.response.body['errors'].first
end
```
Check out [lib/discourse_api/error.rb](lib/discourse_api/error.rb) and [lib/discourse_api/client.rb](lib/discourse_api/client.rb)'s `handle_error` method for the types of errors raised by the API.
If your forum has a basic HTTP authentication enabled, set user and password:
```ruby
client.basic_auth = {
user: "test",
password: "secret"
}
```
## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
## Testing
1. Install discourse locally
2. Inside of your discourse directory, run: `bundle exec rake db:api_test_seed`
3. Start discourse: `bundle exec rails s`
4. Install bundler in the discourse_api directory, run `gem install bundler`
5. Inside of your discourse_api directory, run: `bundle exec rspec spec/`
================================================
FILE: Rakefile
================================================
# frozen_string_literal: true
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
require 'rubocop/rake_task'
RuboCop::RakeTask.new(:rubocop)
task test: :spec
task lint: :rubocop
task default: [:spec, :lint]
================================================
FILE: config_example.yml
================================================
# Specify your environment by making a copy of this file to create a file in your root directory called config.yml with your environment settings.
# host e.g. http://localhost:3000 or https://discourse.my_domain.com
host: YOUR_HOST_NAME
# api user (can effect results returned, e.g. .categories method returns only the categories your api_username can see)
# create a new client with when changing api user
api_username: YOUR_API_USERNAME
# api key from Discourse admin panel /admin/api/keys
api_key: YOUR_API_KEY
================================================
FILE: discourse_api.gemspec
================================================
# frozen_string_literal: true
lib = File.expand_path("lib", __dir__)
$LOAD_PATH.unshift(lib) if !$LOAD_PATH.include?(lib)
require "discourse_api/version"
Gem::Specification.new do |spec|
spec.name = "discourse_api"
spec.version = DiscourseApi::VERSION
spec.authors = ["Sam Saffron", "John Paul Ashenfelter", "Michael Herold", "Blake Erickson"]
spec.email = %w[
sam.saffron@gmail.com
john@ashenfelter.com
michael.j.herold@gmail.com
o.blakeerickson@gmail.com
]
spec.description = "Discourse API"
spec.summary = "Allows access to the Discourse API"
spec.homepage = "http://github.com/discourse/discourse_api"
spec.license = "MIT"
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]
spec.add_runtime_dependency "faraday", "~> 2.7"
spec.add_runtime_dependency "faraday-follow_redirects"
spec.add_runtime_dependency "faraday-multipart"
spec.add_runtime_dependency "rack", ">= 1.6"
spec.add_development_dependency "bundler", "~> 2.0"
spec.add_development_dependency "guard", "~> 2.14"
spec.add_development_dependency "guard-rspec", "~> 4.7"
spec.add_development_dependency "rake", ">= 12.3.3"
spec.add_development_dependency "rb-inotify", "~> 0.9"
spec.add_development_dependency "rspec", "~> 3.4"
spec.add_development_dependency "simplecov", "~> 0.11"
spec.add_development_dependency "webmock", "~> 3.0"
spec.add_development_dependency "rubocop-discourse", "= 3.8.1"
spec.add_development_dependency "syntax_tree", "~> 6.2.0"
spec.required_ruby_version = ">= 2.7.0"
end
================================================
FILE: examples/backups.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# get list of backup files
puts client.backups()
# create backup
puts client.create_backup()
# restore backup
puts client.restore_backup("backup_file_name.tar.gz")
# download backup
puts client.download_backup("backup_file_name.tar.gz", "/tmp/")
================================================
FILE: examples/badges.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# get badges
puts client.badges
# get badges for a user
puts client.user_badges("test-user")
# Grants a badge to a user.
puts client.grant_user_badge(badge_id: 9, username: "test-user", reason: "Really nice person")
# Creates a new badge
###
# Required params:
# :name, :badge_type_id (gold: 1, bronze: 3, silver: 2)
# Optional params:
# :description, :allow_title, :multiple_grant, :icon, :listable,
# :target_posts, :query, :enabled, :auto_revoke, :badge_grouping_id,
# :trigger, :show_posts, :image, :long_description
###
puts client.create_badge(name: "Shiny new badge", badge_type_id: 1)
================================================
FILE: examples/bookmark_topic.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# Bookmark topic
puts client.bookmark_topic(1418)
# Remove bookmark from topic
puts client.remove_topic_bookmark(1418)
================================================
FILE: examples/category.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# get categories
puts client.categories()
# get sub categories for parent category with id 2
puts client.categories(parent_category_id: 2)
# get the full categories response
puts client.categories_full()
# List topics in a category
category_topics = client.category_latest_topics(category_slug: "test-category")
puts category_topics
# List topics in a category paged
category_topics_paged = client.category_latest_topics(category_slug: "test-category", page: "5")
puts category_topics_paged
# update category notification_level
update_response =
client.category_set_user_notification(
id: "test-id",
notification_level: "test-notification-level",
)
puts update_response
================================================
FILE: examples/change_topic_status.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
response =
client.create_topic(
category: 1,
skip_validations: true,
auto_track: false,
title: "Concert Master: A new way to choose",
raw: "This is the raw markdown for my post",
)
# get topic_id from response
topic_id = response["topic_id"]
##
# available options (guessing from reading discourse source)
# status can be: ['autoclose', 'closed', 'archived', 'disabled', 'visible']
# enabled can be: [true, false]
##
# lock topic (note: api_username determines user that is performing action)
params = { status: "closed", enabled: true, api_username: "YOUR USERNAME/USERS USERNAME" }
client.change_topic_status(topic_id, params)
# unlock topic (note: api_username determines user that is performing action)
params = { status: "closed", enabled: false, api_username: "YOUR USERNAME/USERS USERNAME" }
client.change_topic_status(topic_id, params)
================================================
FILE: examples/create_private_message.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
client.create_private_message(
title: "Confidential: Hello World!",
raw: "This is the raw markdown for my private message",
target_usernames: "user1,user2",
)
================================================
FILE: examples/create_topic.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
client.create_topic(
category: 1,
skip_validations: true,
auto_track: false,
title: "Concert Master: A new way to choose",
raw: "This is the raw markdown for my post",
)
# create Poll topic
client.create_topic(
category: 2,
skip_validations: false,
auto_track: false,
title: "Your Favorite Color?",
raw: "[poll name=color]\n- Green\n- Blue\n- Red\n[/poll]",
)
# Create Topic with Tags
client.create_topic(
category: 1,
skip_validations: true,
auto_track: false,
title: "Concert Master: A new way to choose",
raw: "This is the raw markdown for my post",
tags: %w[asdf fdsa],
)
================================================
FILE: examples/create_update_category.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
###
# Required category params:
# :name, :color, :text_color
# Optional category params:
# :slug, :permissions, :auto_close_hours, :auto_close_based_on_last_post, :position, :email_in,
# :email_in_allow_strangers, :logo_url, :background_url, :allow_badges, :topic_template
###
# Create category
new_category = client.create_category(name: "Test Category", color: "AB9364", text_color: "FFFFFF")
puts "Created category: " + new_category.to_s
# Update category
response =
client.update_category(
id: new_category["id"],
name: "The Best Test Category",
slug: "the-best-test-category",
color: "0E76BD",
text_color: "000000",
)
puts "Updated category: " + response.to_s
================================================
FILE: examples/create_user.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# create user
user =
client.create_user(
name: "Bruce Wayne",
email: "bruce@wayne.com",
username: "batman",
password: "WhySoSerious",
)
# activate user
client.activate(user["user_id"])
================================================
FILE: examples/dashboard.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# get all dashboard status as json
puts client.get_dashboard_stats
# get hash of some dashboard total value
puts client.get_dashboard_stats_totals
# sample output: {"users"=>9, "topics"=>230, "posts"=>441}
================================================
FILE: examples/disposable_invite_tokens.rb
================================================
# frozen_string_literal: true
# requires this plugin => https://github.com/discourse/discourse-invite-tokens
require "csv"
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# fetch email-less invite tokens
invite_tokens =
client.disposable_tokens(username: "eviltrout", quantity: 5, group_names: "security,support")
# write to CSV file
CSV.open(File.expand_path("../invite_tokens.csv", __FILE__), "w") do |csv|
invite_tokens.each { |value| csv << [value] }
end
================================================
FILE: examples/example.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# get latest topics
puts client.latest_topics
================================================
FILE: examples/group_set_user_notification_level.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
@target_username = "YOUR_TARGET_USERNAME"
@target_group_id = # YOUR NUMERIC TARGET GROUP ID
@user = client.user(@target_username)
# each user's group and the group's default notification level are stored under user['groups']
@user["groups"].each do |group|
if group["id"] == @target_group_id
@group_name = group["name"]
@default_level = group["default_notification_level"]
end
end
# and the user's notification setting for each group is stored under user['group_users]
@user["group_users"].each do |users_group|
if users_group["group_id"] == @target_group_id
@notification_level = users_group["notification_level"]
puts "Group ID:#{@target_group_id} #{@group_name} Current Notification Level: #{@notification_level} Default: #{@default_level}"
response = client.group_set_user_notification_level(@group_name, @user["id"], @default_level)
puts response
@users_group_users_after_update = client.user(@target_username)["group_users"]
# this just pulls the user from the database again to make sure we updated the user's group notification level
@users_group_users_after_update.each do |users_group_second_pass|
if users_group_second_pass["group_id"] == @target_group_id
puts "Updated ID:#{@target_group_id} #{@group_name} Notification Level: #{users_group_second_pass["notification_level"]} Default: #{@default_level}"
end
end
end
end
================================================
FILE: examples/groups.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
response = client.create_group(name: "engineering_team")
group_id = response["basic_group"]["id"]
client.group_add(group_id, username: "sam")
client.group_add(group_id, username: "jeff")
client.group_add(group_id, usernames: %w[neil dan])
client.group_add(group_id, user_id: 123)
client.group_add(group_id, user_ids: [123, 456])
client.group_remove(group_id, username: "neil")
client.group_remove(group_id, user_id: 123)
client.delete_group(group_id)
## List users of a group
members = client.group_members("trust_level_0")
puts members
================================================
FILE: examples/invite_users.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# invite user
invite = client.invite_user(email: "name@example.com", group_ids: "41,42")
#update invite
client.update_invite(invite["id"], email: "namee@example.com")
# resend invite
client.resend_invite("namee@example.com")
# invite to a topic
client.invite_to_topic(1, email: "foo@bar.com")
# if the user is an admin you may invite to a group as well
client.invite_to_topic(1, email: "foo@bar.com", group_ids: "1,2,3")
# retrieve invite
puts client.retrieve_invite(email: "foo@bar.com")
# resend all invites
client.resend_all_invites
# destroy invite
client.destroy_invite(invite["id"])
# destroy all expired invites
client.destroy_all_expired_invites
================================================
FILE: examples/manage_api_keys.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# generate user api key
response = client.create_api_key(key: { description: "Key to The Batmobile", username: "batman" })
api_key_id = response["key"]["id"]
puts response
# sample output: {"key"=>{"id"=>13, "key"=>"abc", "description"=>"Key to the Batmobile"}}
response = client.revoke_api_key(api_key_id)
puts response
# sample output: {"key"=>{"id"=>13, "key"=>"abc", "description"=>"Key to the Batmobile", "revoked_at"=>"2021-01-01T00:00:00.000Z"}}
response = client.undo_revoke_api_key(api_key_id)
puts response
# sample output: {"key"=>{"id"=>13, "key"=>"abc", "description"=>"Key to the Batmobile", "revoked_at"=>nil}}
response = client.list_api_keys
puts response
# sample output: {"keys"=>[{"id"=>13, "key"=>"abc", "description"=>"Key to the Batmobile"}]}
response = client.delete_api_key(api_key_id)
puts response
# sample output: {"success"=>"OK"}
================================================
FILE: examples/notifications.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# watch an entire category
client.category_set_user_notification_level(1, notification_level: 3)
# mute a topic
client.topic_set_user_notification_level(1, notification_level: 0)
# get user notifications
client.notifications(username: "discourse")
================================================
FILE: examples/polls.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
options = ["8b4736b1ae3dfb5a28088530f036f9e5"]
poll_option_votes = client.poll_vote post_id: 5, poll_name: "poll", options: options
puts poll_option_votes.body["vote"]
poll_option_votes = client.toggle_poll_status(post_id: 5, poll_name: "poll", status: "closed")
puts poll_option_votes[:body]["poll"]["status"]
poll_option_votes = client.poll_voters(post_id: 5, poll_name: "poll")
puts poll_option_votes["voters"]
================================================
FILE: examples/post_action.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# Post Action Type IDs
# 1 - Bookmark
# 2 - Like
# 3 - Flag: Off-topic
# 4 - Flag: Inappropriate
# 5 - Vote
# 6 - Notify User
# 7 - Flag - Notify Moderators
# 8 - Flag - Spam
# Like a post
client.create_post_action(id: 2, post_action_type_id: 2)
# Flag a topic as spam
client.create_topic_action(id: 1, post_action_type_id: 8)
# Unlike a post
client.destroy_post_action(id: 3, post_action_type_id: 2)
================================================
FILE: examples/search.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# search for term
puts client.search("discourse")
================================================
FILE: examples/sent_private_messages.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
client.sent_private_messages("test_user")
================================================
FILE: examples/sso.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
client.sync_sso(
sso_secret: "discourse_sso_rocks",
name: "Test Name",
username: "test_name",
email: "name@example.com",
external_id: "2",
)
================================================
FILE: examples/topic_lists.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# get latest topics
puts client.latest_topics({})
# get top topics
puts client.top_topics
# recategorize topic
puts client.recategorize_topic(topic_id: 108, category_id: 5)
# get all categories
puts client.categories({})
================================================
FILE: examples/update_user.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# update username from "robin" to "batman"
puts client.update_username("robin", "batman")
# update name of user whose username is "batman"
puts client.update_user("batman", name: "Bruce Wayne")
# update email of user whose username is "batman"
puts client.update_email("batman", "batman@gotham.com")
# update avatar of user whose username is "batman"
puts client.update_avatar(
username: "batman",
url: "http://meta-discourse.r.worldssl.net/uploads/default/2497/724a6ef2e79d2bc7.png",
)
# update trust level of user whose id is "102"
puts client.update_trust_level(user_id: 102, level: 2)
# update user bio, location or website
puts client.update_user(
"batman",
bio_raw: "I am Batman.",
location: "Gotham",
website: "https://en.wikipedia.org/wiki/Batman",
)
# log out everywhere and refresh browser of user whose id is "2"
puts client.log_out(2)
================================================
FILE: examples/upload_file.rb
================================================
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
require File.expand_path("../../lib/discourse_api", __FILE__)
config = DiscourseApi::ExampleHelper.load_yml
client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000")
client.api_key = config["api_key"] || "YOUR_API_KEY"
client.api_username = config["api_username"] || "YOUR_USERNAME"
# Upload a file
file = Faraday::UploadIO.new("grumpy_cat.pdf", "application/pdf")
client.upload_file(file: file)
# Upload a file via URL
client.upload_file(url: "https://giphy.com/grumpy_cat.gif")
================================================
FILE: lib/discourse_api/api/api_key.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module ApiKey
def list_api_keys
response = get("/admin/api/keys")
response[:body]
end
def create_api_key(args)
args = API.params(args).required(:key).to_h
post("/admin/api/keys", args)
end
def revoke_api_key(id)
post("/admin/api/keys/#{id}/revoke")
end
def undo_revoke_api_key(id)
post("/admin/api/keys/#{id}/undo-revoke")
end
def delete_api_key(id)
delete("/admin/api/keys/#{id}")
end
end
end
end
================================================
FILE: lib/discourse_api/api/backups.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Backups
def backups
response = get("/admin/backups.json")
response.body
end
def create_backup
post("/admin/backups", with_uploads: true)
end
def restore_backup(file_name)
post("/admin/backups/#{file_name}/restore")
end
def download_backup(file_name, destination)
response = get("/admin/backups/#{file_name}")
# write file
File.open("#{destination}/#{file_name}", "wb") { |fp| fp.write(response.body) }
end
end
end
end
================================================
FILE: lib/discourse_api/api/badges.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Badges
def badges
response = get("/admin/badges.json")
response.body
end
def user_badges(username)
response = get("/user-badges/#{username}.json")
response.body["badges"]
end
def grant_user_badge(params = {})
post("/user_badges", params)
end
def create_badge(params = {})
args =
API
.params(params)
.required(:name, :badge_type_id)
.optional(
:description,
:allow_title,
:multiple_grant,
:icon,
:listable,
:target_posts,
:query,
:enabled,
:auto_revoke,
:badge_grouping_id,
:trigger,
:show_posts,
:image,
:long_description,
)
post("/admin/badges.json", args)
end
end
end
end
================================================
FILE: lib/discourse_api/api/categories.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Categories
# :color and :text_color are RGB hexadecimal strings
# :permissions is a hash with the group name and permission_type which is
# an integer 1 = Full 2 = Create Post 3 = Read Only
def create_category(args = {})
params = common_category_params(args)
response = post("/categories", params.to_h)
response["category"]
end
def update_category(args = {})
category_id = args[:id]
params = common_category_params(args, include_id: true)
response = put("/categories/#{category_id}", params.to_h)
response["body"]["category"] if response["body"]
end
def reorder_categories(args = {})
params = API.params(args).required(:mapping)
post("/categories/reorder", params)
end
def delete_category(id)
response = delete("/categories/#{id}")
response[:body]["success"]
end
def categories(params = {})
categories_full(params)["category_list"]["categories"]
end
def categories_full(params = {})
response = get("/categories.json", params)
response[:body]
end
def category_latest_topics(args = {})
response = category_latest_topics_full(args)
if response["errors"]
response["errors"]
else
response["topic_list"]["topics"]
end
end
def category_latest_topics_full(args = {})
params = API.params(args).required(:category_slug).optional(:page).to_h
url = "/c/#{params[:category_slug]}/l/latest.json"
url = "#{url}?page=#{params[:page]}" if params.include?(:page)
response = get(url)
response[:body]
end
def category_top_topics(category_slug)
response = category_top_topics_full(category_slug)
if response["errors"]
response["errors"]
else
response["topic_list"]["topics"]
end
end
def category_top_topics_full(category_slug)
response = get("/c/#{category_slug}/l/top.json")
response[:body]
end
def category_new_topics(category_slug)
response = category_new_topics_full(category_slug)
response["topic_list"]["topics"]
end
def category_new_topics_full(category_slug)
response = get("/c/#{category_slug}/l/new.json")
response[:body]
end
def category(id)
response = get("/c/#{id}/show")
response[:body]["category"]
end
# TODO: Deprecated. Remove after 20210727
def category_set_user_notification(args = {})
category_id = args[:id]
args = API.params(args).required(:notification_level)
post("/category/#{category_id}/notifications", args)
end
def category_set_user_notification_level(category_id, params)
params = API.params(params).required(:notification_level)
post("/category/#{category_id}/notifications", params)
end
private
def common_category_params(args, include_id: false)
params = API.params(args)
params = params.required(:id) if include_id
params
.required(:name)
.optional(
:color,
:text_color,
:slug,
:permissions,
:auto_close_hours,
:auto_close_based_on_last_post,
:position,
:email_in,
:email_in_allow_strangers,
:logo_url,
:background_url,
:allow_badges,
:topic_template,
:custom_fields,
:description,
:reviewable_by_group_name,
:show_subcategory_list,
:subcategory_list_style,
:allowed_tags,
:allowed_tag_groups,
:required_tag_group_name,
:topic_featured_links_allowed,
:search_priority,
:form_template_ids,
)
.default(parent_category_id: nil)
end
end
end
end
================================================
FILE: lib/discourse_api/api/dashboard.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Dashboard
def get_dashboard_stats
response = get("admin/dashboard.json")
response[:body]
end
def get_dashboard_stats_totals
stats = get_dashboard_stats
global_reports = stats["global_reports"]
users = global_reports[1]
topics = global_reports[3]
posts = global_reports[4]
{ "users" => users["total"], "topics" => topics["total"], "posts" => posts["total"] }
end
end
end
end
================================================
FILE: lib/discourse_api/api/email.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Email
def email_settings
response = get("/admin/email.json")
response.body
end
def list_email(filter)
response = get("/admin/email/#{filter}.json")
response.body
end
end
end
end
================================================
FILE: lib/discourse_api/api/groups.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Groups
def create_group(args)
args =
API
.params(args)
.required(:name)
.default(visibility_level: 0)
.optional(
:mentionable_level,
:messageable_level,
:automatic_membership_email_domains,
:automatic_membership_retroactive,
:title,
:primary_group,
:grant_trust_level,
:incoming_email,
:flair_url,
:flair_bg_color,
:flair_color,
:bio_raw,
:members_visibility_level,
:public_admission,
:public_exit,
:allow_membership_requests,
:full_name,
:default_notification_level,
:usernames,
:owner_usernames,
:membership_request_template,
)
.to_h
post("/admin/groups", group: args)
end
def update_group(group_id, args)
args =
API
.params(args)
.default(visibility_level: 0)
.optional(
:mentionable_level,
:messageable_level,
:name,
:automatic_membership_email_domains,
:title,
:primary_group,
:grant_trust_level,
:incoming_email,
:flair_url,
:flair_bg_color,
:flair_color,
:bio_raw,
:visibility_level,
:public_admission,
:public_exit,
:allow_membership_requests,
:full_name,
:default_notification_level,
:membership_request_template,
)
.to_h
put("/groups/#{group_id}", group: args)
end
def group_add_owners(group_id, args)
args = API.params(args).required(:usernames).to_h
put("/admin/groups/#{group_id}/owners.json", group: args)
end
def group_remove_owners(group_id, args)
args = API.params(args).required(:usernames).to_h
delete("/admin/groups/#{group_id}/owners.json", group: args)
end
def groups(args = {})
params = API.params(args).optional(:page).to_h
url = "/groups.json"
url += "?page=#{params[:page]}" if params.include?(:page)
response = get(url)
response.body
end
def group(group_name)
response = get("/groups/#{group_name}.json")
response.body
end
def group_add(group_id, users)
users.keys.each do |key|
# Accept arrays and convert to comma-delimited string.
users[key] = users[key].join(",") if users[key].respond_to? :join
# Accept non-plural user_id or username, but send pluralized version in the request.
if key.to_s[-1] != "s"
users["#{key}s"] = users[key]
users.delete(key)
end
end
put("/admin/groups/#{group_id}/members.json", users)
end
def group_remove(group_id, users)
users.keys.each do |key|
# Accept arrays and convert to comma-delimited string.
users[key] = users[key].join(",") if users[key].respond_to? :join
# Accept non-plural user_id or username, but send pluralized version in the request.
if key.to_s[-1] != "s"
users["#{key}s"] = users[key]
users.delete(key)
end
end
delete("/admin/groups/#{group_id}/members.json", users)
end
def delete_group(group_id)
delete("/admin/groups/#{group_id}.json")
end
def group_members(group_name, params = {})
options = params
params = API.params(params).optional(:offset, :limit).default(offset: 0, limit: 100).to_h
response = get("/groups/#{group_name}/members.json", params)
if options[:all] == true
response.body
else
response.body["members"]
end
end
def group_set_user_notification_level(group, user_id, notification_level)
post(
"/groups/#{group}/notifications?user_id=#{user_id}¬ification_level=#{notification_level}",
)
end
end
end
end
================================================
FILE: lib/discourse_api/api/invite.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Invite
def invite_user(params = {})
args =
API
.params(params)
.optional(
:email,
:skip_email,
:custom_message,
:max_redemptions_allowed,
:topic_id,
:group_ids,
:expires_at,
)
.to_h
post("/invites", args)
end
# TODO: Deprecated. Remove after 20220506
def invite_user_to_topic(params = {})
deprecated(__method__, "invite_to_topic")
invite_to_topic(params[:topic_id], params)
end
def invite_to_topic(topic_id, params = {})
args = API.params(params).optional(:email, :user, :group_ids, :custom_message).to_h
post("/t/#{topic_id}/invite", args)
end
def retrieve_invite(params = {})
args = API.params(params).required(:email).to_h
response = get("invites/retrieve.json", args)
response.body
end
# requires this plugin => https://github.com/discourse/discourse-invite-tokens
def disposable_tokens(params = {})
post("/invite-token/generate", params)
end
def update_invite(invite_id, params = {})
args =
API
.params(params)
.optional(
:topic_id,
:group_ids,
:group_names,
:email,
:send_email,
:custom_message,
:max_redemptions_allowed,
:expires_at,
)
.to_h
put("invites/#{invite_id}", args)
end
def destroy_all_expired_invites
post("invites/destroy-all-expired")
end
def resend_all_invites
post("invites/reinvite-all")
end
def resend_invite(email)
post("invites/reinvite", { email: email })
end
def destroy_invite(invite_id)
delete("/invites", { id: invite_id })
end
end
end
end
================================================
FILE: lib/discourse_api/api/notifications.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Notifications
def notifications(params = {})
params = API.params(params).optional(:username, :recent, :limit, :offset, :filter)
response = get("/notifications.json", params)
response[:body]
end
end
end
end
================================================
FILE: lib/discourse_api/api/params.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
def self.params(args)
Params.new(args)
end
class Params
def initialize(args)
raise ArgumentError.new("Required to be initialized with a Hash") unless args.is_a? Hash
@args = args
@required = []
@optional = []
@defaults = {}
end
def required(*keys)
@required.concat(keys)
@required.each do |k|
raise ArgumentError.new("#{k} is required but not specified") unless @args.key?(k)
end
self
end
def optional(*keys)
@optional.concat(keys)
self
end
def default(args)
args.each { |k, v| @defaults[k] = v }
self
end
def to_h
h = {}
@required.each { |k| h[k] = @args[k] }
@optional.each { |k| h[k] = @args[k] if @args.include?(k) }
@defaults.each { |k, v| @args.key?(k) ? h[k] = @args[k] : h[k] = v }
h
end
end
end
end
================================================
FILE: lib/discourse_api/api/polls.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Polls
def poll_vote(args)
args = API.params(args).required(:post_id, :poll_name, :options).optional(:created_at)
put("/polls/vote", args)
end
def toggle_poll_status(args)
args =
API
.params(args)
.required(:post_id, :poll_name, :status)
.optional(:api_username)
.optional(:raise_errors)
put("/polls/toggle_status", args)
end
def poll_voters(args)
args = API.params(args).required(:post_id, :poll_name).optional(:opts)
response = get("/polls/voters.json", args)
response[:body]
end
end
end
end
================================================
FILE: lib/discourse_api/api/posts.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Posts
def create_post(args)
args = API.params(args).required(:topic_id, :raw).optional(:created_at, :api_username)
post("/posts", args)
end
def create_post_action(args)
args = API.params(args).required(:id, :post_action_type_id)
post("/post_actions", args.to_h.merge(flag_topic: false))
end
def get_post(id, args = {})
args = API.params(args).optional(:version)
response = get("/posts/#{id}.json", args)
response[:body]
end
def posts(args = {})
args = API.params(args).default(before: 0)
response = get("/posts.json", args)
response[:body]
end
def wikify_post(id)
put("/posts/#{id}/wiki", wiki: true)
end
def edit_post(id, raw)
put("/posts/#{id}", post: { raw: raw })
end
def delete_post(id)
delete("/posts/#{id}.json")
end
def destroy_post_action(post_id, post_action_type_id)
delete("/post_actions/#{post_id}.json", post_action_type_id: post_action_type_id)
end
def post_action_users(post_id, post_action_type_id)
response =
get("/post_action_users.json", id: post_id, post_action_type_id: post_action_type_id)
response[:body]
end
end
end
end
================================================
FILE: lib/discourse_api/api/private_messages.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module PrivateMessages
# TODO: Deprecated. Remove after 20220628
def create_private_message(args = {})
deprecated(__method__, "create_pm")
args[:target_recipients] = args.delete :target_usernames
create_pm(args.to_h)
end
# :target_recipients REQUIRED comma separated list of usernames
# :category OPTIONAL name of category, not ID
# :created_at OPTIONAL seconds since epoch.
def create_pm(args = {})
args[:archetype] = "private_message"
args =
API
.params(args)
.required(:title, :raw, :target_recipients, :archetype)
.optional(:category, :created_at, :api_username)
post("/posts", args.to_h)
end
def private_messages(username, *args)
response = get("topics/private-messages/#{username}.json", args)
response[:body]["topic_list"]["topics"]
end
def sent_private_messages(username, *args)
response = get("topics/private-messages-sent/#{username}.json", args)
response[:body]["topic_list"]["topics"]
end
end
end
end
================================================
FILE: lib/discourse_api/api/search.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Search
# Returns search results that match the specified term.
#
# @param term [String] a search term
# @param options [Hash] A customizable set of options
# @option options [String] :type_filter Returns results of the specified type.
# @return [Array] Return results as an array of Hashes.
def search(term, options = {})
raise ArgumentError.new("#{term} is required but not specified") unless term
raise ArgumentError.new("#{term} is required but not specified") if term.empty?
response = get("/search", options.merge(q: term))
response[:body]
end
end
end
end
================================================
FILE: lib/discourse_api/api/site_settings.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module SiteSettings
def site_setting_update(args = {})
params = API.params(args).required(:name, :value).to_h
new_site_setting = { params[:name] => params[:value] }
put("/admin/site_settings/#{params[:name]}", new_site_setting)
end
end
end
end
================================================
FILE: lib/discourse_api/api/sso.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module SSO
def sync_sso(params = {})
sso = DiscourseApi::SingleSignOn.parse_hash(params)
post("/admin/users/sync_sso", sso.payload)
end
end
end
end
================================================
FILE: lib/discourse_api/api/tags.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Tags
def show_tag(tag)
response = get("/tag/#{tag}")
response[:body]
end
end
end
end
================================================
FILE: lib/discourse_api/api/topics.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Topics
# :category OPTIONAL name of category, not ID
# :skip_validations OPTIONAL boolean
# :auto_track OPTIONAL boolean
# :created_at OPTIONAL seconds since epoch.
def create_topic(args = {})
args =
API
.params(args)
.required(:title, :raw)
.optional(
:skip_validations,
:category,
:auto_track,
:created_at,
:api_username,
:tags,
:external_id,
:visible,
)
post("/posts", args.to_h)
end
def create_topic_action(args)
args = API.params(args).required(:id, :post_action_type_id)
post("/post_actions", args.to_h.merge(flag_topic: true))
end
# timestamp is seconds past the epoch.
def edit_topic_timestamp(topic_id, timestamp)
put("/t/#{topic_id}/change-timestamp", timestamp: timestamp)
end
def latest_topics(params = {})
response = get("/latest.json", params)
response[:body]["topic_list"]["topics"]
end
def top_topics(params = {})
response = get("/top.json", params)
response[:body]["topic_list"]["topics"]
end
def new_topics(params = {})
response = get("/new.json", params)
response[:body]["topic_list"]["topics"]
end
def rename_topic(topic_id, title)
put("/t/#{topic_id}.json", topic_id: topic_id, title: title)
end
def recategorize_topic(topic_id, category_id)
put("/t/#{topic_id}.json", topic_id: topic_id, category_id: category_id)
end
# TODO: Deprecated. Remove after 20201231
def change_topic_status(topic_slug, topic_id, params = {})
deprecated(__method__, "update_topic_status")
update_topic_status(topic_id, params)
end
def update_topic_status(topic_id, params = {})
params = API.params(params).required(:status, :enabled).optional(:api_username)
put("/t/#{topic_id}/status", params)
end
def topic(id, params = {})
response = get("/t/#{id}.json", params)
response[:body]
end
def topics_by(username, params = {})
response = get("/topics/created-by/#{username}.json", params)
response[:body]["topic_list"]["topics"]
end
def delete_topic(id)
delete("/t/#{id}.json")
end
def topic_posts(topic_id, post_ids = [], params = {})
params =
API.params(params).optional(
:asc,
:filter,
:include_raw,
:include_suggested,
:post_number,
:username_filters,
)
url = ["/t/#{topic_id}/posts.json"]
if post_ids.count > 0
url.push("?")
url.push(post_ids.map { |id| "post_ids[]=#{id}" }.join("&"))
end
response = get(url.join, params)
response[:body]
end
def change_owner(topic_id, params = {})
params = API.params(params).required(:username, :post_ids)
post("/t/#{topic_id}/change-owner.json", params)
end
def topic_set_user_notification_level(topic_id, params)
params = API.params(params).required(:notification_level)
post("/t/#{topic_id}/notifications", params)
end
def bookmark_topic(topic_id)
put("/t/#{topic_id}/bookmark.json")
end
def remove_topic_bookmark(topic_id)
put("/t/#{topic_id}/remove_bookmarks.json")
end
def get_topic_url_by_external_id(external_id)
get("/t/external_id/#{external_id}")
end
end
end
end
================================================
FILE: lib/discourse_api/api/uploads.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Uploads
def upload_file(args)
args =
API
.params(args)
.optional(:file, :url, :user_id)
.default(type: "composer", synchronous: true)
.to_h
post("/uploads", args)
end
end
end
end
================================================
FILE: lib/discourse_api/api/user_actions.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module UserActions
def user_replies(username)
params = { username: username, filter: "5" }
response = get("/user_actions.json", params)
response.body["user_actions"]
end
def user_topics_and_replies(username)
params = { username: username, filter: "4,5" }
response = get("/user_actions.json", params)
response.body["user_actions"]
end
end
end
end
================================================
FILE: lib/discourse_api/api/users.rb
================================================
# frozen_string_literal: true
module DiscourseApi
module API
module Users
def activate(id)
put("/admin/users/#{id}/activate")
end
def user(username, params = {})
response = get("/users/#{username}.json", params)
response[:body]["user"]
end
def user_sso(user_id)
response = get("/admin/users/#{user_id}.json")
response[:body]["single_sign_on_record"]
end
def update_avatar(username, args)
args =
API.params(args).optional(:file, :url).default(type: "avatar", synchronous: true).to_h
upload_response = post("/uploads", args)
put("/u/#{username}/preferences/avatar/pick", upload_id: upload_response["id"])
end
def update_email(username, email)
put("/u/#{username}/preferences/email", email: email)
end
def update_user(username, args)
args =
API
.params(args)
.optional(
:name,
:title,
:bio_raw,
:location,
:website,
:profile_background,
:card_background,
:email_messages_level,
:mailing_list_mode,
:homepage_id,
:theme_ids,
:user_fields,
)
.to_h
put("/u/#{username}", args)
end
def update_username(username, new_username)
put("/u/#{username}/preferences/username", new_username: new_username)
end
def update_trust_level(user_id, args)
args = API.params(args).required(:level).to_h
response = put("/admin/users/#{user_id}/trust_level", args)
response[:body]
end
def create_user(args)
args =
API
.params(args)
.required(:name, :email, :password, :username)
.optional(:active, :approved, :staged, :user_fields)
.to_h
post("/users", args)
end
def log_out(id)
post("/admin/users/#{id}/log_out")
end
def list_users(type, params = {})
response = get("admin/users/list/#{type}.json", params)
response[:body]
end
def grant_admin(user_id)
response = put("admin/users/#{user_id}/grant_admin")
response[:body]
end
def revoke_admin(user_id)
response = put("admin/users/#{user_id}/revoke_admin")
response[:body]
end
def grant_moderation(user_id)
response = put("admin/users/#{user_id}/grant_moderation")
response[:body]
end
def revoke_moderation(user_id)
put("admin/users/#{user_id}/revoke_moderation")
end
def by_external_id(external_id)
response = get("/users/by-external/#{external_id}")
response[:body]["user"]
end
def suspend(user_id, suspend_until, reason)
put("/admin/users/#{user_id}/suspend", suspend_until: suspend_until, reason: reason)
end
def unsuspend(user_id)
put("/admin/users/#{user_id}/unsuspend")
end
def anonymize(user_id)
put("/admin/users/#{user_id}/anonymize")
end
def delete_user(user_id, delete_posts = false)
delete("/admin/users/#{user_id}.json?delete_posts=#{delete_posts}")
end
def check_username(username)
response = get("/users/check_username.json?username=#{CGI.escape(username)}")
response[:body]
end
def deactivate(user_id)
put("/admin/users/#{user_id}/deactivate")
end
end
end
end
================================================
FILE: lib/discourse_api/client.rb
================================================
# frozen_string_literal: true
require "faraday"
require "faraday/follow_redirects"
require "faraday/multipart"
require "json"
require "uri"
require "discourse_api/version"
require "discourse_api/api/categories"
require "discourse_api/api/search"
require "discourse_api/api/sso"
require "discourse_api/api/tags"
require "discourse_api/api/topics"
require "discourse_api/api/polls"
require "discourse_api/api/posts"
require "discourse_api/api/users"
require "discourse_api/api/groups"
require "discourse_api/api/invite"
require "discourse_api/api/private_messages"
require "discourse_api/api/notifications"
require "discourse_api/api/badges"
require "discourse_api/api/email"
require "discourse_api/api/api_key"
require "discourse_api/api/backups"
require "discourse_api/api/dashboard"
require "discourse_api/api/uploads"
require "discourse_api/api/user_actions"
require "discourse_api/api/site_settings"
module DiscourseApi
class Client
attr_accessor :api_key
attr_accessor :basic_auth
attr_reader :host, :api_username, :timeout
DEFAULT_TIMEOUT = 30
include DiscourseApi::API::Categories
include DiscourseApi::API::Search
include DiscourseApi::API::SSO
include DiscourseApi::API::Tags
include DiscourseApi::API::Topics
include DiscourseApi::API::Polls
include DiscourseApi::API::Posts
include DiscourseApi::API::Users
include DiscourseApi::API::Groups
include DiscourseApi::API::Invite
include DiscourseApi::API::PrivateMessages
include DiscourseApi::API::Notifications
include DiscourseApi::API::Badges
include DiscourseApi::API::Email
include DiscourseApi::API::ApiKey
include DiscourseApi::API::Backups
include DiscourseApi::API::Dashboard
include DiscourseApi::API::Uploads
include DiscourseApi::API::UserActions
include DiscourseApi::API::SiteSettings
def initialize(host, api_key = nil, api_username = nil)
raise ArgumentError, "host needs to be defined" if host.nil? || host.empty?
@host = host
@api_key = api_key
@api_username = api_username
@use_relative = check_subdirectory(host)
end
def timeout=(timeout)
@timeout = timeout
@connection.options.timeout = timeout if @connection
end
def api_username=(api_username)
@api_username = api_username
@connection.headers["Api-Username"] = api_username unless @connection.nil?
end
def connection_options
@connection_options ||= {
url: @host,
request: {
timeout: @timeout || DEFAULT_TIMEOUT,
},
headers: {
accept: "application/json",
user_agent: user_agent,
},
}
end
def ssl(options)
connection_options[:ssl] = options
end
def delete(path, params = {})
request(:delete, path, params)
end
def get(path, params = {})
request(:get, path, params)
end
def post(path, params = {})
response = request(:post, path, params)
case response.status
when 200, 201, 204
response.body
else
raise DiscourseApi::Error, response.body
end
end
def put(path, params = {})
request(:put, path, params)
end
def patch(path, params = {})
request(:patch, path, params)
end
def user_agent
@user_agent ||= "DiscourseAPI Ruby Gem #{DiscourseApi::VERSION}"
end
def deprecated(old, new)
warn "[DEPRECATED]: `#{old}` is deprecated. Please use `#{new}` instead."
end
private
def connection
@connection ||=
Faraday.new connection_options do |conn|
# Allow uploading of files
conn.request :multipart
# Convert request params to "www-form-encoded"
conn.request :url_encoded
# Allow to interact with forums behind basic HTTP authentication
if basic_auth
conn.request :authorization, :basic, basic_auth[:user], basic_auth[:password]
end
# Follow redirects
conn.response :follow_redirects, limit: 5
# Parse responses as JSON
conn.response :json, content_type: "application/json"
# For HTTP debugging, uncomment
# conn.response :logger
# Use Faraday's default HTTP adapter
conn.adapter Faraday.default_adapter
# Pass api_key and api_username on every request
unless api_username.nil?
conn.headers["Api-Key"] = api_key
conn.headers["Api-Username"] = api_username
end
end
end
def request(method, path, params = {})
unless Hash === params
params = params.to_h if params.respond_to? :to_h
end
path = @use_relative ? path.sub(%r{^/}, "") : path
response = connection.send(method.to_sym, path, params)
handle_error(response)
response.env
rescue Faraday::ClientError, JSON::ParserError
raise DiscourseApi::Error
rescue Faraday::ConnectionFailed
raise DiscourseApi::Timeout
end
def handle_error(response)
case response.status
when 403
raise DiscourseApi::UnauthenticatedError.new(response.env[:body], response.env)
when 404, 410
raise DiscourseApi::NotFoundError.new(response.env[:body], response.env)
when 422
raise DiscourseApi::UnprocessableEntity.new(response.env[:body], response.env)
when 429
raise DiscourseApi::TooManyRequests.new(response.env[:body], response.env)
when 500...600
raise DiscourseApi::Error.new(response.env[:body])
end
end
def check_subdirectory(host)
URI(host).request_uri != "/"
end
end
end
================================================
FILE: lib/discourse_api/error.rb
================================================
# frozen_string_literal: true
module DiscourseApi
class DiscourseError < StandardError
attr_reader :response
def initialize(message, response = nil)
super(message)
@response = response
end
end
class Error < DiscourseError
attr_reader :wrapped_exception
# Initializes a new Error object
#
# @param exception [Exception, String]
# @return [DiscourseApi::Error]
def initialize(exception = $!)
@wrapped_exception = exception
exception.respond_to?(:message) ? super(exception.message) : super(exception.to_s)
end
end
class UnauthenticatedError < DiscourseError
end
class NotFoundError < DiscourseError
end
class UnprocessableEntity < DiscourseError
end
class TooManyRequests < DiscourseError
end
class Timeout < Error
end
end
================================================
FILE: lib/discourse_api/example_helper.rb
================================================
# frozen_string_literal: true
require "yaml"
module DiscourseApi
class ExampleHelper
def self.load_yml
config_yml = File.expand_path("../../../config.yml", __FILE__)
puts config_yml
begin
config = YAML.load_file config_yml
rescue Errno::ENOENT
config = {}
end
config
end
end
end
================================================
FILE: lib/discourse_api/single_sign_on.rb
================================================
# frozen_string_literal: true
require "base64"
require "rack"
require "openssl"
module DiscourseApi
class SingleSignOn
class ParseError < RuntimeError
end
class MissingConfigError < RuntimeError
end
ACCESSORS = %i[
add_groups
admin
avatar_force_update
avatar_url
bio
card_background_url
confirmed_2fa
email
external_id
groups
locale
locale_force_update
moderator
name
no_2fa_methods
nonce
profile_background_url
remove_groups
require_2fa
require_activation
return_sso_url
suppress_welcome_message
title
username
]
FIXNUMS = []
BOOLS = %i[
admin
avatar_force_update
confirmed_2fa
locale_force_update
moderator
no_2fa_methods
require_2fa
require_activation
suppress_welcome_message
]
ARRAYS = [:groups]
#NONCE_EXPIRY_TIME = 10.minutes # minutes is a rails method and is causing an error. Is this needed in the api?
attr_accessor(*ACCESSORS)
attr_writer :custom_fields, :sso_secret, :sso_url
def self.sso_secret
raise MissingConfigError, "sso_secret not implemented on class, be sure to set it on instance"
end
def self.sso_url
raise MissingConfigError, "sso_url not implemented on class, be sure to set it on instance"
end
def self.parse_hash(payload)
sso = new
sso.sso_secret = payload.delete(:sso_secret)
sso.sso_url = payload.delete(:sso_url)
ACCESSORS.each do |k|
val = payload[k]
val = val.to_i if FIXNUMS.include? k
val = %w[true false].include?(val) ? val == "true" : nil if BOOLS.include? k
val = val.split(",") if ARRAYS.include?(k) && !val.nil?
sso.send("#{k}=", val)
end
# Set custom_fields
sso.custom_fields = payload[:custom_fields]
# Add custom_fields (old format)
payload.each do |k, v|
if field = k[/^custom\.(.+)$/, 1]
# Maintain adding of .custom bug
sso.custom_fields["custom.#{field}"] = v
end
end
sso
end
def self.parse(payload, sso_secret = nil)
sso = new
sso.sso_secret = sso_secret if sso_secret
parsed = Rack::Utils.parse_query(payload)
if parsed["sso"].nil? || sso.sign(parsed["sso"]) != parsed["sig"]
diags =
"\n\nsso: #{parsed["sso"].inspect}\n\nsig: #{parsed["sig"].inspect}\n\nexpected sig: #{sso.sign(parsed.fetch("sso", ""))}"
if parsed["sso"].nil? || parsed["sso"] =~ %r{[^a-zA-Z0-9=\r\n/+]}m
raise ParseError,
"The SSO field should be Base64 encoded, using only A-Z, a-z, 0-9, +, /, and = characters. Your input contains characters we don't understand as Base64, see http://en.wikipedia.org/wiki/Base64 #{diags}"
else
raise ParseError, "Bad signature for payload #{diags}"
end
end
decoded = Base64.decode64(parsed["sso"])
decoded_hash = Rack::Utils.parse_query(decoded)
ACCESSORS.each do |k|
val = decoded_hash[k.to_s]
val = val.to_i if FIXNUMS.include? k
val = %w[true false].include?(val) ? val == "true" : nil if BOOLS.include? k
val = val.split(",") if ARRAYS.include?(k) && !val.nil?
sso.send("#{k}=", val)
end
decoded_hash.each do |k, v|
if field = k[/^custom\.(.+)$/, 1]
sso.custom_fields[field] = v
end
end
sso
end
def diagnostics
DiscourseApi::SingleSignOn::ACCESSORS.map { |a| "#{a}: #{send(a)}" }.join("\n")
end
def sso_secret
@sso_secret || self.class.sso_secret
end
def sso_url
@sso_url || self.class.sso_url
end
def custom_fields
@custom_fields ||= {}
end
def sign(payload)
OpenSSL::HMAC.hexdigest("sha256", sso_secret, payload)
end
def to_url(base_url = nil)
base = "#{base_url || sso_url}"
"#{base}#{base.include?("?") ? "&" : "?"}#{payload}"
end
def payload
payload = Base64.strict_encode64(unsigned_payload)
"sso=#{CGI.escape(payload)}&sig=#{sign(payload)}"
end
def unsigned_payload
payload = {}
ACCESSORS.each do |k|
next if (val = send k) == nil
payload[k] = val
end
@custom_fields.each { |k, v| payload["custom.#{k}"] = v.to_s } if @custom_fields
Rack::Utils.build_query(payload)
end
end
end
================================================
FILE: lib/discourse_api/version.rb
================================================
# frozen_string_literal: true
module DiscourseApi
VERSION = "2.1.0"
end
================================================
FILE: lib/discourse_api.rb
================================================
# frozen_string_literal: true
require "discourse_api/api/params"
require "discourse_api/client"
require "discourse_api/error"
require "discourse_api/version"
require "discourse_api/example_helper"
require "discourse_api/single_sign_on"
================================================
FILE: spec/discourse_api/api/api_key_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::ApiKey do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#list_api_keys" do
before do
url = "#{host}/admin/api/keys"
stub_get(url).to_return(
body: fixture("list_api_keys.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.list_api_keys
url = "#{host}/admin/api/keys"
expect(a_get(url)).to have_been_made
end
it "returns the requested api keys" do
keys = client.list_api_keys
expect(keys["keys"]).to be_an Array
expect(keys["keys"].first).to be_a Hash
expect(keys["keys"].first).to have_key("key")
end
end
describe "#create_api_key" do
before do
url = "#{host}/admin/api/keys"
stub_post(url).to_return(
body: fixture("api_key.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.create_api_key(key: { username: "robin" })
url = "#{host}/admin/api/keys"
expect(a_post(url)).to have_been_made
end
it "returns the generated api key" do
api_key = client.create_api_key(key: { username: "robin" })
expect(api_key).to be_a Hash
expect(api_key["key"]).to have_key("key")
end
end
describe "#revoke_api_key" do
before do
url = "#{host}/admin/api/keys/10/revoke"
stub_post(url).to_return(
body: fixture("api_key.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.revoke_api_key(10)
url = "#{host}/admin/api/keys/10/revoke"
expect(a_post(url)).to have_been_made
end
it "returns the api key" do
api_key = client.revoke_api_key(10)
expect(api_key["key"]).to have_key("key")
end
end
describe "#undo_revoke_api_key" do
before do
url = "#{host}/admin/api/keys/10/undo-revoke"
stub_post(url).to_return(
body: fixture("api_key.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.undo_revoke_api_key(10)
url = "#{host}/admin/api/keys/10/undo-revoke"
expect(a_post(url)).to have_been_made
end
it "returns the api key" do
api_key = client.undo_revoke_api_key(10)
expect(api_key["key"]).to have_key("key")
end
end
describe "#delete_api_key" do
before do
url = "#{host}/admin/api/keys/10"
stub_delete(url).to_return(
body: '{"success": "OK"}',
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.delete_api_key(10)
url = "#{host}/admin/api/keys/10"
expect(a_delete(url)).to have_been_made
end
it "returns 200" do
response = client.delete_api_key(10)
expect(response["status"]).to eq(200)
end
end
end
================================================
FILE: spec/discourse_api/api/backups_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Backups do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#backups" do
before do
stub_get("#{host}/admin/backups.json").to_return(
body: fixture("backups.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.backups
expect(a_get("#{host}/admin/backups.json")).to have_been_made
end
it "returns the requested backups" do
backups = client.backups
expect(backups).to be_an Array
expect(backups.first).to be_a Hash
expect(backups.first).to have_key("filename")
end
end
end
================================================
FILE: spec/discourse_api/api/badges_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Badges do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#badges" do
before do
stub_get("#{host}/admin/badges.json").to_return(
body: fixture("badges.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.badges
expect(a_get("#{host}/admin/badges.json")).to have_been_made
end
it "returns the requested badges" do
badges = client.badges
expect(badges).to be_a Hash
expect(badges["badges"]).to be_an Array
end
end
describe "#user-badges" do
before do
stub_get("#{host}/user-badges/test_user.json").to_return(
body: fixture("user_badges.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.user_badges("test_user")
expect(a_get("#{host}/user-badges/test_user.json")).to have_been_made
end
it "returns the requested user badges" do
badges = client.user_badges("test_user")
expect(badges).to be_an Array
expect(badges.first).to be_a Hash
expect(badges.first).to have_key("badge_type_id")
end
end
end
================================================
FILE: spec/discourse_api/api/categories_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Categories do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#categories" do
before do
stub_get("#{host}/categories.json").to_return(
body: fixture("categories.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.categories
expect(a_get("#{host}/categories.json")).to have_been_made
end
it "returns the requested categories" do
categories = client.categories
expect(categories).to be_an Array
expect(categories.first).to be_a Hash
end
it "returns the requested categories with hash arg" do
categories = client.categories({})
expect(categories).to be_an Array
expect(categories.first).to be_a Hash
end
end
describe "#categories_full" do
before do
stub_get("#{host}/categories.json").to_return(
body: fixture("categories.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.categories
expect(a_get("#{host}/categories.json")).to have_been_made
end
it "returns the entire categories response" do
categories = client.categories_full
expect(categories).to be_a Hash
expect(categories).to have_key "category_list"
expect(categories).to have_key "featured_users"
end
end
describe "#category_latest_topics" do
before do
stub_get("#{host}/c/category-slug/l/latest.json").to_return(
body: fixture("category_latest_topics.json"),
headers: {
content_type: "application/json",
},
)
end
it "returns the latest topics in a category" do
latest_topics = client.category_latest_topics(category_slug: "category-slug")
expect(latest_topics).to be_an Array
end
end
describe "#category_latest_topics_full" do
before do
stub_get("#{host}/c/category-slug/l/latest.json").to_return(
body: fixture("category_latest_topics.json"),
headers: {
content_type: "application/json",
},
)
end
it "returns the entire latest topics in a category response" do
latest_topics = client.category_latest_topics_full(category_slug: "category-slug")
expect(latest_topics).to be_a Hash
expect(latest_topics).to have_key "topic_list"
expect(latest_topics).to have_key "users"
end
end
describe "#category_top_topics" do
before do
stub_get("#{host}/c/category-slug/l/top.json").to_return(
body: fixture("category_topics.json"),
headers: {
content_type: "application/json",
},
)
end
it "returns the top topics in a category" do
topics = client.category_top_topics("category-slug")
expect(topics).to be_an Array
end
end
describe "#category_top_topics_full" do
before do
stub_get("#{host}/c/category-slug/l/top.json").to_return(
body: fixture("category_topics.json"),
headers: {
content_type: "application/json",
},
)
end
it "returns the entire top topics in a category response" do
topics = client.category_top_topics_full("category-slug")
expect(topics).to be_a Hash
expect(topics).to have_key "topic_list"
expect(topics).to have_key "users"
end
end
describe "#category_new_topics" do
before do
stub_get("#{host}/c/category-slug/l/new.json").to_return(
body: fixture("category_topics.json"),
headers: {
content_type: "application/json",
},
)
end
it "returns the new topics in a category" do
topics = client.category_new_topics("category-slug")
expect(topics).to be_an Array
end
end
describe "#category_new_topics_full" do
before do
stub_get("#{host}/c/category-slug/l/new.json").to_return(
body: fixture("category_topics.json"),
headers: {
content_type: "application/json",
},
)
end
it "returns the new topics in a category" do
topics = client.category_new_topics_full("category-slug")
expect(topics).to be_a Hash
expect(topics).to have_key "topic_list"
expect(topics).to have_key "users"
end
end
describe "#category_new_category" do
before do
stub_post("#{host}/categories")
client.create_category(
name: "test_category",
color: "283890",
text_color: "FFFFFF",
description: "This is a description",
permissions: {
"group_1" => 1,
"admins" => 1,
},
)
end
it "makes a create category request" do
expect(
a_post("#{host}/categories").with(
body:
"color=283890&description=This+is+a+description&name=test_category&parent_category_id&permissions%5Badmins%5D=1&permissions%5Bgroup_1%5D=1&text_color=FFFFFF",
),
).to have_been_made
end
end
describe "#reorder_categories" do
before do
stub_post("#{host}/categories/reorder").to_return(
body: fixture("notification_success.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes a categories reordering request" do
payload = { "1": 2, "2": 3, "3": 4, "4": 1 }
response = client.reorder_categories(mapping: payload.to_json)
expect(
a_post("#{host}/categories/reorder").with(
body: "mapping=#{CGI.escape(payload.to_json.to_s)}",
),
).to have_been_made
expect(response["success"]).to eq("OK")
end
end
describe "#category_set_user_notification" do
before do
stub_post("#{host}/category/1/notifications").to_return(
body: fixture("notification_success.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the post request" do
response = client.category_set_user_notification(id: 1, notification_level: 3)
expect(a_post("#{host}/category/1/notifications")).to have_been_made
expect(response["success"]).to eq("OK")
end
end
describe "#category_set_user_notification_level" do
before do
stub_post("#{host}/category/1/notifications").to_return(
body: fixture("notification_success.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the post request" do
response = client.category_set_user_notification_level(1, notification_level: 3)
expect(
a_post("#{host}/category/1/notifications").with(body: "notification_level=3"),
).to have_been_made
expect(response["success"]).to eq("OK")
end
end
end
================================================
FILE: spec/discourse_api/api/email_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Email do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#email_settings" do
before do
stub_get("#{host}/admin/email.json").to_return(
body: fixture("email_settings.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.email_settings
expect(a_get("#{host}/admin/email.json")).to have_been_made
end
it "returns the requested settings" do
settings = client.email_settings
expect(settings).to be_a Hash
expect(settings).to have_key("delivery_method")
expect(settings).to have_key("settings")
end
end
describe "#list_email_all" do
before do
stub_get("#{host}/admin/email/all.json").to_return(
body: fixture("email_list_all.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.list_email("all")
expect(a_get("#{host}/admin/email/all.json")).to have_been_made
end
it "returns all email" do
all_email = client.list_email("all")
expect(all_email).to be_an Array
end
end
end
================================================
FILE: spec/discourse_api/api/groups_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Groups do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#groups" do
before do
stub_get("#{host}/groups.json").to_return(
body: fixture("groups.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.groups
expect(a_get("#{host}/groups.json")).to have_been_made
end
it "returns the requested groups" do
groups = client.groups
expect(groups).to be_an Array
groups.each { |g| expect(g).to be_a Hash }
end
it "returns a single group" do
stub_get("#{host}/groups/some-group.json").to_return(
body: fixture("group.json"),
headers: {
content_type: "application/json",
},
)
group = client.group("some-group")
expect(group["basic_group"]).to be_a Hash
end
it "create new groups" do
stub_post("#{host}/admin/groups")
client.create_group(name: "test_group")
params = escape_params("group[name]" => "test_group", "group[visibility_level]" => 0)
expect(a_post("#{host}/admin/groups").with(body: params)).to have_been_made
end
it "update an existing group" do
stub_put("#{host}/groups/42")
client.update_group(42, name: "test_group")
params = escape_params("group[name]" => "test_group", "group[visibility_level]" => 0)
expect(a_put("#{host}/groups/42").with(body: params)).to have_been_made
end
describe "add members" do
before { stub_request(:put, "#{host}/admin/groups/123/members.json") }
it "adds a single member by username" do
client.group_add(123, username: "sam")
expect(
a_request(:put, "#{host}/admin/groups/123/members.json").with(body: { usernames: "sam" }),
).to have_been_made
end
it "adds an array of members by username" do
client.group_add(123, usernames: %w[sam jeff])
expect(
a_request(:put, "#{host}/admin/groups/123/members.json").with(
body: {
usernames: "sam,jeff",
},
),
).to have_been_made
end
it "adds a single member by user_id" do
client.group_add(123, user_id: 456)
expect(
a_request(:put, "#{host}/admin/groups/123/members.json").with(body: { user_ids: "456" }),
).to have_been_made
end
it "adds an array of members by user_id" do
client.group_add(123, user_id: [123, 456])
expect(
a_request(:put, "#{host}/admin/groups/123/members.json").with(
body: {
user_ids: "123,456",
},
),
).to have_been_made
end
end
describe "remove members" do
let(:url) { "#{host}/admin/groups/123/members.json?usernames=sam" }
before { stub_delete(url) }
it "removes member" do
client.group_remove(123, username: "sam")
expect(a_delete(url)).to have_been_made
end
end
describe "add owners" do
let(:url) { "#{host}/admin/groups/123/owners.json" }
before { stub_put(url) }
it "makes the member an owner" do
client.group_add_owners(123, usernames: "sam")
params = escape_params("group[usernames]" => "sam")
expect(
a_request(:put, "#{host}/admin/groups/123/owners.json").with(body: params),
).to have_been_made
end
end
describe "remove owners" do
let(:url) { "#{host}/admin/groups/123/owners.json?group%5Busernames%5D=sam" }
before { stub_delete(url) }
it "removes the owner role from the group member" do
client.group_remove_owners(123, usernames: "sam")
expect(a_delete(url)).to have_been_made
end
end
describe "group members" do
it "list members" do
stub_get("#{host}/groups/mygroup/members.json?limit=100&offset=0").to_return(
body: fixture("members_0.json"),
headers: {
content_type: "application/json",
},
)
stub_get("#{host}/groups/mygroup/members.json?limit=100&offset=100").to_return(
body: fixture("members_1.json"),
headers: {
content_type: "application/json",
},
)
members = client.group_members("mygroup")
expect(a_get("#{host}/groups/mygroup/members.json?limit=100&offset=0")).to have_been_made
expect(members.length).to eq(100)
members = client.group_members("mygroup", offset: 100)
expect(a_get("#{host}/groups/mygroup/members.json?limit=100&offset=100")).to have_been_made
expect(members.length).to eq(90)
end
context "with :all params" do
it "lists members and owners" do
stub_get("#{host}/groups/mygroup/members.json?limit=100&offset=0").to_return(
body: fixture("members_2.json"),
headers: {
content_type: "application/json",
},
)
member_data = client.group_members("mygroup", all: true)
expect(a_get("#{host}/groups/mygroup/members.json?limit=100&offset=0")).to have_been_made
expect(member_data["members"].length).to eq(100)
expect(member_data["owners"].length).to eq(7)
expect(member_data.keys.sort).to eq(%w[members meta owners])
end
end
end
describe "group user notification level" do
before { stub_post("#{host}/groups/mygroup/notifications?user_id=77¬ification_level=3") }
it "updates user's notification level for group" do
client.group_set_user_notification_level("mygroup", 77, 3)
expect(
a_post("#{host}/groups/mygroup/notifications?user_id=77¬ification_level=3"),
).to have_been_made
end
end
end
end
================================================
FILE: spec/discourse_api/api/invite_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Invite do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#invite_user" do
before do
stub_post("#{host}/invites").to_return(
body: fixture("topic_invite_user.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.invite_user(email: "fake_user@example.com", group_ids: "41,42")
expect(a_post("#{host}/invites")).to have_been_made
end
it "returns success" do
response = client.invite_user(email: "fake_user@example.com", group_ids: "41,42")
expect(response).to be_a Hash
expect(response["success"]).to be_truthy
end
end
describe "#update_invite" do
before do
stub_put("#{host}/invites/27").to_return(
body: fixture("topic_invite_user.json"),
headers: {
content_type: "application/json",
},
)
end
it "updates invite" do
client.update_invite(27, email: "namee@example.com")
expect(a_put("#{host}/invites/27")).to have_been_made
end
end
describe "#retrieve_invite" do
before do
stub_get("#{host}/invites/retrieve.json?email=foo@bar.com").to_return(
body: fixture("retrieve_invite.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.retrieve_invite(email: "foo@bar.com")
expect(a_get("#{host}/invites/retrieve.json?email=foo@bar.com")).to have_been_made
end
it "returns the requested topics" do
invites = client.retrieve_invite(email: "foo@bar.com")
expect(invites).to be_an Hash
end
it "returns the requested invite" do
invites = client.retrieve_invite(email: "foo@bar.com")
expect(invites["email"]).to eq("foo@bar.com")
expect(invites).to have_key("invite_key")
end
end
describe "#destroy_all_expired_invites" do
let(:url) { "#{host}/invites/destroy-all-expired" }
before do
stub_post(url).to_return(
body: '{"success": "OK"}',
headers: {
content_type: "application/json",
},
)
end
it "destroys all expired invites" do
client.destroy_all_expired_invites
expect(a_post(url)).to have_been_made
end
end
describe "#resend_all_invites" do
let(:url) { "#{host}/invites/reinvite-all" }
before do
stub_post(url).to_return(
body: '{"success": "OK"}',
headers: {
content_type: "application/json",
},
)
end
it "resends all invites" do
client.resend_all_invites
expect(a_post(url)).to have_been_made
end
end
describe "#resend_invite" do
let(:url) { "#{host}/invites/reinvite" }
before do
stub_post(url).to_return(
body: '{"success": "OK"}',
headers: {
content_type: "application/json",
},
)
end
it "resends invite" do
client.resend_invite("foo@bar.com")
expect(a_post(url)).to have_been_made
end
end
describe "#destroy_invite" do
let(:url) { "#{host}/invites?id=27" }
before do
stub_delete(url).to_return(
body: '{"success": "OK"}',
headers: {
content_type: "application/json",
},
)
end
it "destroys the invite" do
client.destroy_invite(27)
expect(a_delete(url)).to have_been_made
end
end
end
================================================
FILE: spec/discourse_api/api/notifications_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Notifications do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#notifications" do
before do
stub_get("#{host}/notifications.json").to_return(
body: fixture("notifications.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.notifications
expect(a_get("#{host}/notifications.json")).to have_been_made
end
it "returns the requested notifications" do
notifications = client.notifications
expect(notifications).to be_an Array
expect(notifications.first).to be_an Hash
expect(notifications[0]["notification_type"]).to eq(9)
end
end
end
================================================
FILE: spec/discourse_api/api/params_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Params do
def params_for(h)
DiscourseApi::API::Params.new(h).required(:r1).optional(:o1, :o2).default(d1: "default")
end
it "should raise on missing required params" do
expect { params_for({ o1: "test" }).to_h }.to raise_error(ArgumentError)
end
it "should not raise when a required param is false" do
expect { params_for({ r1: false }).to_h }.not_to raise_error
end
it "should not include optional params when not provided" do
expect(params_for({ r1: "test" }).to_h).not_to include(:o1)
end
it "should include optional params if provided but blank" do
expect(params_for({ r1: "test", o2: nil }).to_h).to include(:o2)
end
it "should include default params when defined but not provided" do
expect(params_for({ r1: "test" }).to_h).to include(d1: "default")
end
it "should include default params when defined and provided" do
expect(params_for({ r1: "test", d1: "override" }).to_h).to include(d1: "override")
end
it "should include optional and default params when defined and provided" do
expect(params_for({ r1: "test", o1: "optional", d1: "override" }).to_h).to include(
o1: "optional",
d1: "override",
)
end
end
================================================
FILE: spec/discourse_api/api/polls_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Polls do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#poll vote" do
before do
path = "#{host}/polls/vote"
stub_put(path).to_return(
body: fixture("polls_vote.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
options = ["8b4736b1ae3dfb5a28088530f036f9e5"]
client.poll_vote post_id: 5, poll_name: "poll", options: options
expect(a_put("#{host}/polls/vote")).to have_been_made
end
it "returns the expected votes" do
options = ["8b4736b1ae3dfb5a28088530f036f9e5"]
vote = client.poll_vote post_id: 5, poll_name: "poll", options: options
expect(vote.body).to be_a Hash
expect(vote.body["poll"]["options"]).to be_an Array
expect(vote.body["vote"]).to eq(["8b4736b1ae3dfb5a28088530f036f9e5"])
end
describe "#poll toggle_status" do
before do
path = "#{host}/polls/toggle_status"
stub_put(path).to_return(
body: fixture("polls_toggle_status.json"),
headers: {
content_type: "application/json",
},
)
end
it "toggles the poll status to closed" do
client.toggle_poll_status post_id: 5, poll_name: "poll", status: "closed"
expect(a_put("#{host}/polls/toggle_status")).to have_been_made
end
it "returns the expected results of closed poll" do
returned_poll_status =
client.toggle_poll_status post_id: 5, poll_name: "poll", status: "closed"
expect(returned_poll_status.body).to be_a Hash
returned_poll_status.body["poll"]["options"].each { |g| expect(g).to be_a Hash }
end
end
describe "#poll voters" do
before do
stub_get("#{host}/polls/voters.json?post_id=5&poll_name=poll").to_return(
body: fixture("polls_voters.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.poll_voters post_id: 5, poll_name: "poll"
expect(a_get("#{host}/polls/voters.json?post_id=5&poll_name=poll")).to have_been_made
end
it "returns the expected votes" do
voters = client.poll_voters post_id: 5, poll_name: "poll"
expect(voters).to be_a Hash
voters.each { |g| expect(g).to be_an Array }
expect(voters["voters"]["e539a9df8700d0d05c69356a07b768cf"]).to be_an Array
expect(voters["voters"]["e539a9df8700d0d05c69356a07b768cf"][0]["id"]).to eq(356)
end
end
end
end
================================================
FILE: spec/discourse_api/api/posts_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Posts do
let(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#get_post" do
before do
stub_get("#{host}/posts/11.json").to_return(
body: fixture("post.json"),
headers: {
content_type: "application/json",
},
)
end
it "fetches a post" do
the_post = client.get_post(11)
expect(the_post).to be_a Hash
expect(the_post["id"]).to eq(11)
end
end
describe "#posts" do
before do
stub_get("#{host}/posts.json?before=0").to_return(
body: fixture("posts_latest.json"),
headers: {
content_type: "application/json",
},
)
stub_get("#{host}/posts.json?before=14").to_return(
body: fixture("posts_before.json"),
headers: {
content_type: "application/json",
},
)
end
it "fetches latest posts" do
the_posts = client.posts()
expect(the_posts).to be_a Hash
expect(the_posts["latest_posts"][0]["id"]).to eq(15)
end
it "fetches posts before 14" do
the_posts = client.posts(before: 14)
expect(the_posts).to be_a Hash
expect(the_posts["latest_posts"][0]["id"]).to eq(14)
end
end
describe "#wikify_post" do
before { stub_put("#{host}/posts/9999/wiki") }
it "fails on unknown post" do
client.wikify_post(9999)
expect(a_put("#{host}/posts/9999/wiki")).to have_been_made
end
end
describe "#delete_post" do
before { stub_delete("#{host}/posts/9999.json") }
it "deletes a post" do
client.delete_post(9999)
expect(a_delete("#{host}/posts/9999.json")).to have_been_made
end
end
describe "#post_action_users" do
before do
stub_get("#{host}/post_action_users.json?id=11&post_action_type_id=2").to_return(
body: fixture("post_action_users.json"),
headers: {
content_type: "application/json",
},
)
end
it "fetches post_action_users" do
post_action_users = client.post_action_users(11, 2)
expect(post_action_users).to be_a Hash
expect(post_action_users["post_action_users"][0]["id"]).to eq(1286)
end
end
end
================================================
FILE: spec/discourse_api/api/private_messages_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::PrivateMessages do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#private_messages" do
before do
stub_get("#{host}/topics/private-messages/test_user.json").to_return(
body: fixture("private_messages.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.private_messages("test_user")
expect(a_get("#{host}/topics/private-messages/test_user.json")).to have_been_made
end
it "returns the requested private messages" do
private_messages = client.private_messages("test_user")
expect(private_messages).to be_an Array
end
end
describe "#sent_private_messages" do
before do
stub_get("#{host}/topics/private-messages-sent/test_user.json").to_return(
body: fixture("private_messages.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.sent_private_messages("test_user")
expect(a_get("#{host}/topics/private-messages-sent/test_user.json")).to have_been_made
end
it "returns the requested sent private messages" do
private_messages = client.sent_private_messages("test_user")
expect(private_messages).to be_an Array
end
end
describe "#create_pm" do
before do
stub_post("#{host}/posts")
client.create_pm(
title: "Confidential: Hello World!",
raw: "This is the raw markdown for my private message",
target_recipients: "user1,user2",
)
end
it "makes a create private message request" do
expect(
a_post("#{host}/posts").with(
body:
"archetype=private_message&raw=This+is+the+raw+markdown+for+my+private+message&target_recipients=user1%2Cuser2&title=Confidential%3A+Hello+World%21",
),
).to have_been_made
end
end
end
================================================
FILE: spec/discourse_api/api/search_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Search do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#search" do
before do
stub_get("#{host}/search").with(query: { q: "test" }).to_return(
body: fixture("search.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.search("test")
expect(a_get("#{host}/search").with(query: { q: "test" })).to have_been_made
end
it "returns the requested search" do
results = client.search("test")
expect(results).to be_an Array
expect(results.first).to be_a Hash
end
it "raises an ArgumentError for nil" do
expect { client.search(nil) }.to raise_error(ArgumentError)
end
it "raises an ArgumentError for empty string" do
expect { client.search("") }.to raise_error(ArgumentError)
end
end
end
================================================
FILE: spec/discourse_api/api/site_settings_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::SiteSettings do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#site_setting_update" do
before do
stub_put("#{host}/admin/site_settings/foo")
client.site_setting_update(name: "foo", value: "bar")
end
it "makes a site_settings_update request" do
expect(a_put("#{host}/admin/site_settings/foo").with(body: "foo=bar")).to have_been_made
end
end
end
================================================
FILE: spec/discourse_api/api/sso_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::SSO do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
let(:params) do
{
:sso_secret => "abc",
:sso_url => "www.google.com",
:name => "Some User",
:username => "some_user",
:email => "some@email.com",
:external_id => "abc",
:suppress_welcome_message => false,
:avatar_url => "https://www.website.com",
:title => "ruby",
:avatar_force_update => false,
:add_groups => %w[a b],
:remove_groups => %w[c d],
# old format (which results in custom.custom.field_1 in unsigned_payload)
"custom.field_1" => "tomato",
# new format
:custom_fields => {
field_2: "potato",
},
}
end
let(:expected_unsigned_payload) do
"add_groups=a&add_groups=b&avatar_url=https%3A%2F%2Fwww.website.com" \
"&email=some%40email.com&external_id=abc&name=Some+User&remove_groups=c" \
"&remove_groups=d&title=ruby&username=some_user&custom.field_2=potato" \
"&custom.custom.field_1=tomato"
end
let(:sso_double) { DiscourseApi::SingleSignOn.parse_hash(params) }
describe "#sync_sso" do
before do
stub_post(/.*sync_sso.*/).to_return(
body: fixture("user.json"),
headers: {
content_type: "application/json",
},
)
end
it "assigns params to sso instance" do
allow(DiscourseApi::SingleSignOn).to(receive(:parse_hash).with(params).and_return(sso_double))
client.sync_sso(params)
expect(sso_double.custom_fields).to eql(
{ "custom.field_1" => "tomato", :field_2 => "potato" },
)
expect(sso_double.unsigned_payload).to eql(expected_unsigned_payload)
end
it "requests the correct resource" do
client.sync_sso({ :sso_secret => "test_d7fd0429940", "custom.riffle_url" => "test" })
expect(a_post(/.*sync_sso.*/)).to have_been_made
end
end
end
================================================
FILE: spec/discourse_api/api/topics_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Topics do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#change_topic_status" do
before do
stub_put("#{host}/t/57/status").to_return(
body: fixture("topic.json"),
headers: {
content_type: "application/json",
},
)
end
it "changes the topic status" do
client.update_topic_status(57, { status: "visible", enabled: false })
expect(a_put("#{host}/t/57/status")).to have_been_made
end
end
describe "#invite_to_topic" do
before do
stub_post("#{host}/t/12/invite").to_return(
body: fixture("topic_invite_user.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.invite_to_topic(12, email: "fake_user@example.com")
expect(a_post("#{host}/t/12/invite")).to have_been_made
end
it "returns success" do
response = client.invite_to_topic(12, email: "fake_user@example.com")
expect(response).to be_a Hash
expect(response["success"]).to be_truthy
end
end
describe "#latest_topics" do
before do
stub_get("#{host}/latest.json").to_return(
body: fixture("latest.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.latest_topics
expect(a_get("#{host}/latest.json")).to have_been_made
end
it "returns the requested topics" do
topics = client.latest_topics
expect(topics).to be_an Array
expect(topics.first).to be_a Hash
end
it "can take a hash param" do
topics = client.latest_topics({})
expect(topics).to be_an Array
expect(topics.first).to be_a Hash
end
end
describe "#top_topics" do
before do
stub_get("#{host}/top.json").to_return(
body: fixture("top.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.top_topics
expect(a_get("#{host}/top.json")).to have_been_made
end
it "returns the requested topics" do
topics = client.top_topics
expect(topics).to be_an Array
expect(topics.first).to be_a Hash
end
it "can take a hash param" do
topics = client.top_topics({})
expect(topics).to be_an Array
expect(topics.first).to be_a Hash
end
end
describe "#new_topics" do
before do
stub_get("#{host}/new.json").to_return(
body: fixture("new.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.new_topics
expect(a_get("#{host}/new.json")).to have_been_made
end
it "returns the requested topics" do
client.api_username = "test_user"
topics = client.new_topics
expect(topics).to be_an Array
expect(topics.first).to be_a Hash
end
end
describe "#topic" do
before do
stub_get("#{host}/t/57.json").to_return(
body: fixture("topic.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.topic(57)
expect(a_get("#{host}/t/57.json")).to have_been_made
end
it "returns the requested topic" do
topic = client.topic(57)
expect(topic).to be_a Hash
expect(topic["id"]).to eq(57)
end
end
describe "#update_topic" do
before do
stub_put("#{host}/t/57.json").to_return(
body: fixture("topic.json"),
headers: {
content_type: "application/json",
},
)
end
it "renames the topic" do
client.rename_topic(57, "A new title!")
expect(a_put("#{host}/t/57.json")).to have_been_made
end
it "assigns the topic a new category" do
client.recategorize_topic(57, 3)
expect(a_put("#{host}/t/57.json")).to have_been_made
end
end
describe "#topics_by" do
before do
stub_get("#{host}/topics/created-by/test.json").to_return(
body: fixture("topics_created_by.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.topics_by("test")
expect(a_get("#{host}/topics/created-by/test.json")).to have_been_made
end
it "returns the requested topics" do
topics = client.topics_by("test")
expect(topics).to be_an Array
expect(topics.first).to be_a Hash
end
end
describe "#topic_posts" do
before do
stub_get(%r{#{host}/t/57/posts\.json}).to_return(
body: fixture("topic_posts.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.topic_posts(57)
expect(a_get("#{host}/t/57/posts.json")).to have_been_made
end
it "allows scoping to specific post ids" do
client.topic_posts(57, [123, 456])
expect(a_get("#{host}/t/57/posts.json?post_ids[]=123&post_ids[]=456")).to have_been_made
end
it "returns the requested topic posts" do
body = client.topic_posts(57, [123])
expect(body).to be_a Hash
expect(body["post_stream"]["posts"]).to be_an Array
expect(body["post_stream"]["posts"].first).to be_a Hash
end
it "can retrieve a topic posts' raw attribute" do
body = client.topic_posts(57, [123], { include_raw: true })
expect(body).to be_a Hash
expect(body["post_stream"]["posts"]).to be_an Array
expect(body["post_stream"]["posts"].first).to be_a Hash
expect(body["post_stream"]["posts"].first["raw"]).to be_an Array
end
end
describe "#create_topic_with_tags" do
before do
stub_post("#{host}/posts").to_return(
body: fixture("create_topic_with_tags.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the post request" do
client.create_topic title: "Sample Topic Title",
raw: "Sample topic content body",
tags: %w[asdf fdsa]
expect(a_post("#{host}/posts")).to have_been_made
end
it "returns success" do
response =
client.create_topic title: "Sample Topic Title",
raw: "Sample topic content body",
tags: %w[asdf fdsa]
expect(response).to be_a Hash
expect(response["topic_id"]).to eq 21
end
end
describe "#topic_set_user_notification_level" do
before do
stub_post("#{host}/t/1/notifications").to_return(
body: fixture("notification_success.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the post request" do
response = client.topic_set_user_notification_level(1, notification_level: 3)
expect(
a_post("#{host}/t/1/notifications").with(body: "notification_level=3"),
).to have_been_made
expect(response["success"]).to eq("OK")
end
end
describe "#bookmark_topic" do
before do
stub_put("#{host}/t/1/bookmark.json").to_return(
body: "",
headers: {
content_type: "application/json",
},
)
end
it "makes the put request" do
response = client.bookmark_topic(1)
expect(a_put("#{host}/t/1/bookmark.json")).to have_been_made
expect(response.body).to eq(nil)
end
end
describe "#remove_topic_bookmark" do
before do
stub_put("#{host}/t/1/remove_bookmarks.json").to_return(
body: "",
headers: {
content_type: "application/json",
},
)
end
it "makes the put request" do
response = client.remove_topic_bookmark(1)
expect(a_put("#{host}/t/1/remove_bookmarks.json")).to have_been_made
expect(response.body).to eq(nil)
end
end
end
================================================
FILE: spec/discourse_api/api/uploads_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Uploads do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#upload_file" do
before do
stub_post("#{host}/uploads").to_return(
body: fixture("upload_file.json"),
headers: {
content_type: "application/json",
},
)
end
it "uploads an image via URL" do
image =
"https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/sam/120/5243.png"
args = { url: image }
response = client.upload_file(args)
expect(response["url"]).to eq(
"/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg",
)
end
it "uploads a file" do
file = Faraday::UploadIO.new("spec/fixtures/upload_file.json", "application/json")
args = { file: file }
response = client.upload_file(args)
expect(response["url"]).to eq(
"/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg",
)
end
end
end
================================================
FILE: spec/discourse_api/api/user_actions_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::UserActions do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#user_replies" do
before do
stub_get("#{host}/user_actions.json?username=testuser&filter=5").to_return(
body: fixture("replies.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.user_replies("testuser")
expect(a_get("#{host}/user_actions.json?username=testuser&filter=5")).to have_been_made
end
it "returns the requested user" do
replies = client.user_replies("testuser")
expect(replies).to be_an Array
end
end
describe "#user_topics_and_replies" do
before do
stub_get("#{host}/user_actions.json?username=testuser&filter=4,5").to_return(
body: fixture("replies_and_topics.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.user_topics_and_replies("testuser")
expect(a_get("#{host}/user_actions.json?username=testuser&filter=4,5")).to have_been_made
end
it "returns the requested user" do
replies = client.user_topics_and_replies("testuser")
expect(replies).to be_an Array
end
end
end
================================================
FILE: spec/discourse_api/api/users_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::API::Users do
subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") }
describe "#user" do
before do
stub_get("#{host}/users/test.json").to_return(
body: fixture("user.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.user("test")
expect(a_get("#{host}/users/test.json")).to have_been_made
end
it "returns the requested user" do
user = client.user("test")
expect(user).to be_a Hash
end
it "works with optional params" do
user = client.user("test", {})
expect(user).to be_a Hash
end
end
describe "#user_sso" do
before do
stub_get("#{host}/admin/users/15.json").to_return(
body: fixture("admin_user.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.user_sso(15)
expect(a_get("#{host}/admin/users/15.json")).to have_been_made
end
it "has single_sign_on_record" do
user_sso = client.user_sso(15)
expect(user_sso).to be_a Hash
expect(user_sso).to have_key("external_id")
end
end
describe "#update_avatar" do
before do
stub_post("#{host}/uploads").to_return(
body: fixture("upload_avatar.json"),
headers: {
content_type: "application/json",
},
)
stub_put("#{host}/u/test_user/preferences/avatar/pick").to_return(
body: fixture("user_update_avatar_success.json"),
headers: {
content_type: "application/json",
},
)
end
it "uploads an image" do
sam =
"https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/sam/120/5243.png"
args = { url: sam }
response = client.update_avatar("test_user", args)
expect(response[:body]["success"]).to eq("OK")
end
end
describe "#update_email" do
before do
stub_put("#{host}/u/fake_user/preferences/email").to_return(
body: fixture("user_update_user.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the put request" do
client.update_email("fake_user", "fake_user_2@example.com")
expect(a_put("#{host}/u/fake_user/preferences/email")).to have_been_made
end
it "returns success" do
response = client.update_email("fake_user", "fake_user_2@example.com")
expect(response[:body]["success"]).to be_truthy
end
end
describe "#update_user" do
before do
stub_put("#{host}/u/fake_user").to_return(
body: fixture("user_update_user.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the put request" do
client.api_key = "test_d7fd0429940"
client.api_username = "test_user"
client.update_user("fake_user", name: "Fake User 2")
expect(a_put("#{host}/u/fake_user")).to have_been_made
end
it "returns success" do
client.api_key = "test_d7fd0429940"
client.api_username = "test_user"
response = client.update_user("fake_user", name: "Fake User 2")
expect(response[:body]["success"]).to be_truthy
end
end
describe "#update_username" do
before do
stub_put("#{host}/u/fake_user/preferences/username").to_return(
body: fixture("user_update_username.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the put request" do
client.update_username("fake_user", "fake_user_2")
expect(a_put("#{host}/u/fake_user/preferences/username")).to have_been_made
end
it "returns the updated username" do
response = client.update_username("fake_user", "fake_user_2")
expect(response[:body]["username"]).to eq("fake_user_2")
end
end
describe "#create_user" do
before do
stub_post("#{host}/users").to_return(
body: fixture("user_create_success.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the post request" do
client.create_user name: "Test User",
email: "test2@example.com",
password: "P@ssword",
username: "test2"
expect(a_post("#{host}/users")).to have_been_made
end
it "returns success" do
response =
client.create_user name: "Test User",
email: "test2@example.com",
password: "P@ssword",
username: "test2"
expect(response).to be_a Hash
expect(response["success"]).to be_truthy
end
end
describe "#activate_user" do
before do
stub_put("#{host}/admin/users/15/activate").to_return(
body: fixture("user_activate_success.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the put request" do
client.activate(15)
expect(a_put("#{host}/admin/users/15/activate")).to have_been_made
end
it "returns success" do
response = client.activate(15)
expect(response[:body]["success"]).to eq("OK")
end
end
describe "#log_out_success" do
before do
stub_post("#{host}/admin/users/4/log_out").to_return(
body: fixture("user_log_out_success.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes a post request" do
client.log_out(4)
expect(a_post("#{host}/admin/users/4/log_out")).to have_been_made
end
it "returns success" do
response = client.log_out(4)
expect(response).to be_a Hash
expect(response["success"]).to eq("OK")
end
end
describe "#log_out_unsuccessful" do
before do
stub_post("#{host}/admin/users/90/log_out").to_return(
status: 404,
headers: {
content_type: "application/json",
},
)
end
it "Raises API Error" do
expect { client.log_out(90) }.to raise_error DiscourseApi::NotFoundError
end
end
describe "#list_users" do
before do
stub_get("#{host}/admin/users/list/active.json").to_return(
body: fixture("user_list.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.list_users("active")
expect(a_get("#{host}/admin/users/list/active.json")).to have_been_made
end
it "returns the requested users" do
users = client.list_users("active")
expect(users).to be_an Array
expect(users.first).to be_a Hash
end
end
describe "#update_trust_level" do
before do
url = "#{host}/admin/users/2/trust_level"
stub_put(url).to_return(
body: fixture("update_trust_level.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the correct put request" do
params = { level: 2 }
client.update_trust_level(2, params)
url = "#{host}/admin/users/2/trust_level"
expect(a_put(url)).to have_been_made
end
it "updates the trust_level" do
params = { level: 2 }
admin_user = client.update_trust_level(2, params)
expect(admin_user).to be_a Hash
expect(admin_user["admin_user"]).to have_key("trust_level")
end
end
describe "#grant admin" do
before do
url = "#{host}/admin/users/11/grant_admin"
stub_put(url).to_return(
body: fixture("user_grant_admin.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the correct put request" do
client.grant_admin(11)
url = "#{host}/admin/users/11/grant_admin"
expect(a_put(url)).to have_been_made
end
it "makes the user an admin" do
result = client.grant_admin(11)
expect(result).to be_a Hash
expect(result["admin_user"]["admin"]).to eq(true)
end
end
describe "#grant moderation" do
before do
url = "#{host}/admin/users/11/grant_moderation"
stub_put(url).to_return(
body: fixture("user_grant_moderator.json"),
headers: {
content_type: "application/json",
},
)
end
it "makes the correct put request" do
client.grant_moderation(11)
url = "#{host}/admin/users/11/grant_moderation"
expect(a_put(url)).to have_been_made
end
it "makes the user a moderator" do
result = client.grant_moderation(11)
expect(result).to be_a Hash
expect(result["admin_user"]["moderator"]).to eq(true)
end
end
describe "#revoke moderation" do
before do
url = "#{host}/admin/users/11/revoke_moderation"
stub_put(url).to_return(body: "", status: 200)
end
it "makes the correct put request" do
result = client.revoke_moderation(11)
url = "#{host}/admin/users/11/revoke_moderation"
expect(a_put(url)).to have_been_made
expect(result.status).to eq(200)
end
end
describe "#by_external_id" do
before do
stub_get("#{host}/users/by-external/1").to_return(
body: fixture("user.json"),
headers: {
content_type: "application/json",
},
)
end
it "requests the correct resource" do
client.by_external_id(1)
expect(a_get("#{host}/users/by-external/1")).to have_been_made
end
it "returns the requested user" do
user = client.by_external_id(1)
expect(user["id"]).to eq 1
end
end
describe "#suspend" do
before do
url = "#{host}/admin/users/11/suspend"
stub_put(url).to_return(body: "", status: 200)
end
it "makes the correct put request" do
result = client.suspend(11, "2030-01-01", "no reason")
url = "#{host}/admin/users/11/suspend"
expect(a_put(url)).to have_been_made
expect(result.status).to eq(200)
end
end
describe "#unsuspend" do
before do
url = "#{host}/admin/users/11/unsuspend"
stub_put(url).to_return(body: "", status: 200)
end
it "makes the correct put request" do
result = client.unsuspend(11)
url = "#{host}/admin/users/11/unsuspend"
expect(a_put(url)).to have_been_made
expect(result.status).to eq(200)
end
end
describe "#anonymize" do
before do
url = "#{host}/admin/users/11/anonymize"
stub_put(url).to_return(body: "", status: 200)
end
it "makes the correct put request" do
result = client.anonymize(11)
url = "#{host}/admin/users/11/anonymize"
expect(a_put(url)).to have_been_made
expect(result.status).to eq(200)
end
end
describe "#delete_user" do
before do
url = "#{host}/admin/users/11.json?delete_posts=true"
stub_delete(url).to_return(body: '{"deleted": true}', status: 200)
end
it "makes the correct delete request" do
result = client.delete_user(11, true)
url = "#{host}/admin/users/11.json?delete_posts=true"
expect(a_delete(url)).to have_been_made
expect(result.body).to eq('{"deleted": true}')
expect(result.status).to eq(200)
end
end
describe "#check_username" do
let(:url) { "#{host}/users/check_username.json?username=sparrow" }
let(:body) { '{"available":false,"suggestion":"sparrow1"}' }
before { stub_get(url).to_return(body: body, headers: { content_type: "application/json" }) }
it "requests the correct resource" do
client.check_username("sparrow")
expect(a_get(url)).to have_been_made
end
it "returns the result" do
result = client.check_username("sparrow")
expect(result["available"]).to eq false
end
context "when non-URI characters are used" do
let(:url) { "#{host}/users/check_username.json?username=1_%5B4%5D%21+%40the%24%23%3F" }
let(:body) { '{"errors":["must only include numbers, letters, dashes, and underscores"]}' }
it "escapes them" do
client.check_username("1_[4]! @the$#?")
expect(a_get(url)).to have_been_made
end
it "returns the result" do
result = client.check_username("1_[4]! @the$#?")
expect(
result["errors"].first,
).to eq "must only include numbers, letters, dashes, and underscores"
end
end
end
describe "#deactivate" do
before { stub_put("#{host}/admin/users/15/deactivate").to_return(body: nil) }
it "makes the put request" do
client.deactivate(15)
expect(a_put("#{host}/admin/users/15/deactivate")).to have_been_made
end
it "returns success" do
response = client.deactivate(15)
expect(response.status).to eq(200)
end
end
end
================================================
FILE: spec/discourse_api/client_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::Client do
subject(:client) { DiscourseApi::Client.new(host) }
describe ".new" do
it "requires a host argument" do
expect { DiscourseApi::Client.new }.to raise_error ArgumentError
end
it "defaults api key to nil" do
expect(client.api_key).to be_nil
end
it "defaults api username to nil" do
expect(client.api_username).to be_nil
end
it "accepts an api key argument" do
client = DiscourseApi::Client.new(host, "test")
expect(client.api_key).to eq("test")
end
it "accepts an api username argument" do
client = DiscourseApi::Client.new(host, "test", "test_user")
expect(client.api_username).to eq("test_user")
end
end
describe "#timeout" do
context "with a custom timeout" do
it "is set to Faraday connection" do
expect(client.send(:connection).options.timeout).to eq(30)
end
end
context "with the default timeout" do
it "is set to Faraday connection" do
client.timeout = 25
expect(client.send(:connection).options.timeout).to eq(25)
end
end
it "raises DiscourseApi::Timeout" do
stub_get("#{host}/t/1.json").to_timeout
expect { client.topic(1) }.to raise_error(DiscourseApi::Timeout)
end
end
describe "#api_key" do
it "is publicly accessible" do
client.api_key = "test_d7fd0429940"
expect(client.api_key).to eq("test_d7fd0429940")
end
end
describe "#api_username" do
it "is publicly accessible" do
client.api_username = "test_user"
expect(client.api_username).to eq("test_user")
end
end
describe "#host" do
it "is publicly readable" do
expect(client.host).to eq("#{host}")
end
it "is not publicly writeable" do
expect(client).not_to respond_to(:host=)
end
end
describe "#connection" do
it "looks like a Faraday connection" do
expect(client.send(:connection)).to respond_to :run_request
end
it "memorizes the connection" do
c1, c2 = client.send(:connection), client.send(:connection)
expect(c1.object_id).to eq(c2.object_id)
end
end
describe "#delete" do
before do
stub_delete("#{host}/test/delete").with(query: { deleted: "object" })
client.api_key = "test_d7fd0429940"
client.api_username = "test_user"
end
it "allows custom delete requests" do
client.delete("/test/delete", { deleted: "object" })
expect(a_delete("#{host}/test/delete").with(query: { deleted: "object" })).to have_been_made
end
context "when using a host with a subdirectory" do
subject(:client) { DiscourseApi::Client.new("#{host}/forum") }
before { stub_delete("#{host}/forum/test/delete").with(query: { deleted: "object" }) }
it "allows custom delete requests" do
client.delete("/test/delete", { deleted: "object" })
expect(
a_delete("#{host}/forum/test/delete").with(query: { deleted: "object" }),
).to have_been_made
end
end
end
describe "#post" do
before do
stub_post("#{host}/test/post").with(body: { created: "object" })
client.api_key = "test_d7fd0429940"
client.api_username = "test_user"
end
it "allows custom post requests" do
client.post("/test/post", { created: "object" })
expect(a_post("#{host}/test/post").with(body: { created: "object" })).to have_been_made
end
context "when using a host with a subdirectory" do
subject(:client) { DiscourseApi::Client.new("#{host}/forum") }
before { stub_post("#{host}/forum/test/post").with(body: { created: "object" }) }
it "allows custom post requests" do
client.post("/test/post", { created: "object" })
expect(
a_post("#{host}/forum/test/post").with(body: { created: "object" }),
).to have_been_made
end
end
end
describe "#put" do
before do
stub_put("#{host}/test/put").with(body: { updated: "object" })
client.api_key = "test_d7fd0429940"
client.api_username = "test_user"
end
it "allows custom put requests" do
client.put("/test/put", { updated: "object" })
expect(a_put("#{host}/test/put").with(body: { updated: "object" })).to have_been_made
end
context "when using a host with a subdirectory" do
subject(:client) { DiscourseApi::Client.new("#{host}/forum") }
before { stub_put("#{host}/forum/test/put").with(body: { updated: "object" }) }
it "allows custom post requests" do
client.put("/test/put", { updated: "object" })
expect(a_put("#{host}/forum/test/put").with(body: { updated: "object" })).to have_been_made
end
end
end
describe "#request" do
it "catches 500 errors" do
connection = instance_double(Faraday::Connection)
allow(connection).to receive(:get).and_return(
OpenStruct.new(env: { body: "error page html" }, status: 500),
)
allow(Faraday).to receive(:new).and_return(connection)
expect { client.send(:request, :get, "/test") }.to raise_error DiscourseApi::Error
end
it "catches Faraday errors" do
allow(Faraday).to receive(:new).and_raise(Faraday::ClientError.new("BOOM!"))
expect { client.send(:request, :get, "/test") }.to raise_error DiscourseApi::Error
end
it "catches JSON::ParserError errors" do
allow(Faraday).to receive(:new).and_raise(JSON::ParserError.new("unexpected token"))
expect { client.send(:request, :get, "/test") }.to raise_error DiscourseApi::Error
end
end
end
================================================
FILE: spec/discourse_api/single_sign_on_spec.rb
================================================
# frozen_string_literal: true
require "spec_helper"
describe DiscourseApi::SingleSignOn do
describe DiscourseApi::SingleSignOn::MissingConfigError do
it "inherits from RuntimeError for backward compatibility" do
expect(DiscourseApi::SingleSignOn::MissingConfigError).to be < RuntimeError
end
end
describe DiscourseApi::SingleSignOn::ParseError do
it "inherits from RuntimeError for backward compatibility" do
expect(DiscourseApi::SingleSignOn::ParseError).to be < RuntimeError
end
end
describe ".sso_secret" do
it "raises MissingConfigError when sso_secret is not present" do
expect { described_class.sso_secret }.to raise_error(
DiscourseApi::SingleSignOn::MissingConfigError,
)
end
end
describe ".sso_url" do
it "raises MissingConfigError when sso_url is not present" do
expect { described_class.sso_url }.to raise_error(
DiscourseApi::SingleSignOn::MissingConfigError,
)
end
end
describe ".parse" do
context "when sso is present" do
it "raises ParseError when there's a signature mismatch" do
sso = described_class.new
sso.sso_secret = "abcd"
expect { described_class.parse(sso.payload, "dcba") }.to raise_error(
DiscourseApi::SingleSignOn::ParseError,
)
end
end
context "when sso is missing" do
it "raises ParseError when there's a signature mismatch" do
sso = described_class.new
sso.sso_secret = "abcd"
missing_sso = Rack::Utils.parse_query(sso.payload)
missing_sso.delete("sso")
malformed_query = Rack::Utils.build_query(missing_sso)
expect { described_class.parse(malformed_query, "dcba") }.to raise_error(
DiscourseApi::SingleSignOn::ParseError,
/The SSO field should/i,
)
end
end
end
end
================================================
FILE: spec/fixtures/admin_user.json
================================================
{
"id": 4,
"username": "47f6974294f6e3eb9686",
"avatar_template": "/letter_avatar_proxy/v2/letter/4/8e8cbc/{size}.png",
"active": false,
"admin": false,
"moderator": false,
"last_seen_at": null,
"last_emailed_at": null,
"created_at": "2017-06-23T16:30:22.539Z",
"last_seen_age": null,
"last_emailed_age": null,
"created_at_age": "17m",
"username_lower": "47f6974294f6e3eb9686",
"trust_level": 0,
"trust_level_locked": false,
"flag_level": 0,
"title": null,
"suspended_at": null,
"suspended_till": null,
"suspended": null,
"blocked": false,
"time_read": "< 1m",
"staged": false,
"days_visited": 0,
"posts_read_count": 0,
"topics_entered": 0,
"post_count": 0,
"name": "47f6974294f6e3eb9686f898f98a67d7",
"can_send_activation_email": true,
"can_activate": true,
"can_deactivate": true,
"ip_address": "192.168.56.1",
"registration_ip_address": null,
"can_grant_admin": true,
"can_revoke_admin": false,
"can_grant_moderation": true,
"can_revoke_moderation": false,
"can_impersonate": true,
"like_count": 0,
"like_given_count": 0,
"topic_count": 0,
"flags_given_count": 0,
"flags_received_count": 0,
"private_topics_count": 0,
"can_delete_all_posts": true,
"can_be_deleted": true,
"can_be_anonymized": true,
"suspend_reason": null,
"primary_group_id": null,
"badge_count": 0,
"warnings_received_count": 0,
"bounce_score": 0,
"reset_bounce_score_after": null,
"can_view_action_logs": true,
"single_sign_on_record": {
"user_id": 4,
"external_id": "847",
"last_payload": "nonce=bcbe1f1b1202fe597feaa2e3f23d566c&name=47f6974294f6e3eb9686f898f98a67d7&username=47f6974294f6e3eb9686f898f98a67d7&email=47f6974294f6e3eb9686f898f98a67d7%40test.com&require_activation=true&external_id=847",
"created_at": "2017-06-23T16:30:22.658Z",
"updated_at": "2017-06-23T16:30:22.658Z",
"external_username": "47f6974294f6e3eb9686f898f98a67d7",
"external_email": "47f6974294f6e3eb9686f898f98a67d7@test.com",
"external_name": "47f6974294f6e3eb9686f898f98a67d7",
"external_avatar_url": null
},
"approved_by": null,
"suspended_by": null,
"groups": [
{
"id": 10,
"automatic": true,
"name": "trust_level_0",
"display_name": "trust_level_0",
"user_count": 6,
"alias_level": 0,
"visible": true,
"automatic_membership_email_domains": null,
"automatic_membership_retroactive": false,
"primary_group": false,
"title": null,
"grant_trust_level": null,
"incoming_email": null,
"has_messages": false,
"flair_url": null,
"flair_bg_color": null,
"flair_color": null,
"bio_raw": null,
"bio_cooked": null,
"public": false,
"allow_membership_requests": false,
"full_name": null,
"default_notification_level": 3
}
]
}
================================================
FILE: spec/fixtures/api_key.json
================================================
{
"key": {
"id": 5,
"key": "e722e04df8cf6d097d565ca04eea1ff8e9e8f09beb405bae6f0c79852916f334",
"user": {
"id": 2,
"username": "robin",
"uploaded_avatar_id": 3,
"avatar_template": "/user_avatar/localhost/robin/{size}/3.png"
}
}
}
================================================
FILE: spec/fixtures/backups.json
================================================
[
{
"filename": "discourse-2015-01-10-065015.tar.gz",
"size": 557075,
"link": "//localhost:3000/admin/backups/discourse-2015-01-10-065015.tar.gz"
},
{
"filename": "2014-02-10-065935.tar.gz",
"size": 5,
"link": "//localhost:3000/admin/backups/2014-02-10-065935.tar.gz"
}
]
================================================
FILE: spec/fixtures/badges.json
================================================
{
"badges": [
{
"id": 9,
"name": "Autobiographer",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 1,
"system": true,
"query": " SELECT u.id user_id, current_timestamp granted_at\n FROM users u\n JOIN user_profiles up on u.id = up.user_id\n WHERE bio_raw IS NOT NULL AND LENGTH(TRIM(bio_raw)) > 10 AND\n uploaded_avatar_id IS NOT NULL AND\n (:backfill OR u.id IN (:user_ids) )\n",
"trigger": 8,
"target_posts": false,
"auto_revoke": true,
"show_posts": false,
"badge_type_id": 3
},
{
"id": 11,
"name": "First Like",
"description": null,
"grant_count": 1,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 1,
"system": true,
"query": " SELECT pa1.user_id, pa1.created_at granted_at, pa1.post_id\n FROM (\n SELECT pa.user_id, min(pa.id) id\n FROM post_actions pa\n JOIN badge_posts p on p.id = pa.post_id\n WHERE post_action_type_id = 2 AND\n (:backfill OR pa.post_id IN (:post_ids) )\n GROUP BY pa.user_id\n ) x\n JOIN post_actions pa1 on pa1.id = x.id\n",
"trigger": 1,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 3
},
{
"id": 14,
"name": "First Link",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 1,
"system": true,
"query": " SELECT l.user_id, l.post_id, l.created_at granted_at\n FROM\n (\n SELECT MIN(l1.id) id\n FROM topic_links l1\n JOIN badge_posts p1 ON p1.id = l1.post_id\n JOIN badge_posts p2 ON p2.id = l1.link_post_id\n WHERE NOT reflection AND p1.topic_id <> p2.topic_id AND not quote AND\n (:backfill OR ( p1.id in (:post_ids) ))\n GROUP BY l1.user_id\n ) ids\n JOIN topic_links l ON l.id = ids.id\n",
"trigger": 2,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 3
},
{
"id": 15,
"name": "First Quote",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 1,
"system": true,
"query": " SELECT ids.user_id, q.post_id, q.created_at granted_at\n FROM\n (\n SELECT p1.user_id, MIN(q1.id) id\n FROM quoted_posts q1\n JOIN badge_posts p1 ON p1.id = q1.post_id\n JOIN badge_posts p2 ON p2.id = q1.quoted_post_id\n WHERE (:backfill OR ( p1.id IN (:post_ids) ))\n GROUP BY p1.user_id\n ) ids\n JOIN quoted_posts q ON q.id = ids.id\n",
"trigger": 2,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 3
},
{
"id": 12,
"name": "First Share",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 1,
"system": true,
"query": " SELECT views.user_id, i2.post_id, i2.created_at granted_at\n FROM\n (\n SELECT i.user_id, MIN(i.id) i_id\n FROM incoming_links i\n JOIN badge_posts p on p.id = i.post_id\n WHERE i.user_id IS NOT NULL\n GROUP BY i.user_id\n ) as views\n JOIN incoming_links i2 ON i2.id = views.i_id\n",
"trigger": 0,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 3
},
{
"id": 16,
"name": "Read Guidelines",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 1,
"system": true,
"query": " SELECT user_id, read_faq granted_at\n FROM user_stats\n WHERE read_faq IS NOT NULL AND (user_id IN (:user_ids) OR :backfill)\n",
"trigger": 8,
"target_posts": false,
"auto_revoke": true,
"show_posts": false,
"badge_type_id": 3
},
{
"id": 17,
"name": "Reader",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 1,
"system": true,
"query": " SELECT id user_id, current_timestamp granted_at\n FROM users\n WHERE id IN\n (\n SELECT pt.user_id\n FROM post_timings pt\n JOIN badge_posts b ON b.post_number = pt.post_number AND\n b.topic_id = pt.topic_id\n JOIN topics t ON t.id = pt.topic_id\n LEFT JOIN user_badges ub ON ub.badge_id = 17 AND ub.user_id = pt.user_id\n WHERE ub.id IS NULL AND t.posts_count > 100\n GROUP BY pt.user_id, pt.topic_id, t.posts_count\n HAVING count(*) >= t.posts_count\n )\n",
"trigger": null,
"target_posts": false,
"auto_revoke": false,
"show_posts": false,
"badge_type_id": 3
},
{
"id": 23,
"name": "Great Share",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": true,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 2,
"system": true,
"query": " SELECT views.user_id, i2.post_id, i2.created_at granted_at\n FROM\n (\n SELECT i.user_id, MIN(i.id) i_id\n FROM incoming_links i\n JOIN badge_posts p on p.id = i.post_id\n WHERE i.user_id IS NOT NULL\n GROUP BY i.user_id,i.post_id\n HAVING COUNT(*) > 1000\n ) as views\n JOIN incoming_links i2 ON i2.id = views.i_id\n",
"trigger": 0,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 1
},
{
"id": 22,
"name": "Good Share",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": true,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 2,
"system": true,
"query": " SELECT views.user_id, i2.post_id, i2.created_at granted_at\n FROM\n (\n SELECT i.user_id, MIN(i.id) i_id\n FROM incoming_links i\n JOIN badge_posts p on p.id = i.post_id\n WHERE i.user_id IS NOT NULL\n GROUP BY i.user_id,i.post_id\n HAVING COUNT(*) > 300\n ) as views\n JOIN incoming_links i2 ON i2.id = views.i_id\n",
"trigger": 0,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 2
},
{
"id": 10,
"name": "Editor",
"description": null,
"grant_count": 1,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 2,
"system": true,
"query": " SELECT p.user_id, min(p.id) post_id, min(p.created_at) granted_at\n FROM badge_posts p\n WHERE p.self_edits > 0 AND\n (:backfill OR p.id IN (:post_ids) )\n GROUP BY p.user_id\n",
"trigger": 2,
"target_posts": false,
"auto_revoke": true,
"show_posts": false,
"badge_type_id": 3
},
{
"id": 13,
"name": "First Flag",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 2,
"system": true,
"query": " SELECT pa1.user_id, pa1.created_at granted_at, pa1.post_id\n FROM (\n SELECT pa.user_id, min(pa.id) id\n FROM post_actions pa\n JOIN badge_posts p on p.id = pa.post_id\n WHERE post_action_type_id IN (3,4,7,8) AND\n (:backfill OR pa.post_id IN (:post_ids) )\n GROUP BY pa.user_id\n ) x\n JOIN post_actions pa1 on pa1.id = x.id\n",
"trigger": 1,
"target_posts": true,
"auto_revoke": false,
"show_posts": false,
"badge_type_id": 3
},
{
"id": 21,
"name": "Nice Share",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": true,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 2,
"system": true,
"query": " SELECT views.user_id, i2.post_id, i2.created_at granted_at\n FROM\n (\n SELECT i.user_id, MIN(i.id) i_id\n FROM incoming_links i\n JOIN badge_posts p on p.id = i.post_id\n WHERE i.user_id IS NOT NULL\n GROUP BY i.user_id,i.post_id\n HAVING COUNT(*) > 25\n ) as views\n JOIN incoming_links i2 ON i2.id = views.i_id\n",
"trigger": 0,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 3
},
{
"id": 5,
"name": "Welcome",
"description": null,
"grant_count": 1,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 2,
"system": true,
"query": " SELECT p.user_id, min(post_id) post_id, min(pa.created_at) granted_at\n FROM post_actions pa\n JOIN badge_posts p on p.id = pa.post_id\n WHERE post_action_type_id = 2 AND\n (:backfill OR pa.post_id IN (:post_ids) )\n GROUP BY p.user_id\n",
"trigger": 1,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 3
},
{
"id": 8,
"name": "Great Post",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": true,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 3,
"system": true,
"query": "\n SELECT p.user_id, p.id post_id, p.updated_at granted_at\n FROM badge_posts p\n WHERE p.post_number > 1 AND p.like_count >= 50 AND\n (:backfill OR p.id IN (:post_ids) )\n",
"trigger": 1,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 1
},
{
"id": 20,
"name": "Great Topic",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": true,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 3,
"system": true,
"query": "\n SELECT p.user_id, p.id post_id, p.updated_at granted_at\n FROM badge_posts p\n WHERE p.post_number = 1 AND p.like_count >= 50 AND\n (:backfill OR p.id IN (:post_ids) )\n",
"trigger": 1,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 1
},
{
"id": 7,
"name": "Good Post",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": true,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 3,
"system": true,
"query": "\n SELECT p.user_id, p.id post_id, p.updated_at granted_at\n FROM badge_posts p\n WHERE p.post_number > 1 AND p.like_count >= 25 AND\n (:backfill OR p.id IN (:post_ids) )\n",
"trigger": 1,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 2
},
{
"id": 19,
"name": "Good Topic",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": true,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 3,
"system": true,
"query": "\n SELECT p.user_id, p.id post_id, p.updated_at granted_at\n FROM badge_posts p\n WHERE p.post_number = 1 AND p.like_count >= 25 AND\n (:backfill OR p.id IN (:post_ids) )\n",
"trigger": 1,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 2
},
{
"id": 6,
"name": "Nice Post",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": true,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 3,
"system": true,
"query": "\n SELECT p.user_id, p.id post_id, p.updated_at granted_at\n FROM badge_posts p\n WHERE p.post_number > 1 AND p.like_count >= 10 AND\n (:backfill OR p.id IN (:post_ids) )\n",
"trigger": 1,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 3
},
{
"id": 18,
"name": "Nice Topic",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": true,
"icon": "fa-certificate",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 3,
"system": true,
"query": "\n SELECT p.user_id, p.id post_id, p.updated_at granted_at\n FROM badge_posts p\n WHERE p.post_number = 1 AND p.like_count >= 10 AND\n (:backfill OR p.id IN (:post_ids) )\n",
"trigger": 1,
"target_posts": true,
"auto_revoke": true,
"show_posts": true,
"badge_type_id": 3
},
{
"id": 4,
"name": "Leader",
"description": null,
"grant_count": 0,
"allow_title": true,
"multiple_grant": false,
"icon": "fa-user",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 4,
"system": true,
"query": "\n SELECT u.id user_id, current_timestamp granted_at FROM users u\n WHERE trust_level >= 4 AND (\n :backfill OR u.id IN (:user_ids)\n )\n",
"trigger": 4,
"target_posts": false,
"auto_revoke": true,
"show_posts": false,
"badge_type_id": 1
},
{
"id": 3,
"name": "Regular",
"description": null,
"grant_count": 0,
"allow_title": true,
"multiple_grant": false,
"icon": "fa-user",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 4,
"system": true,
"query": "\n SELECT u.id user_id, current_timestamp granted_at FROM users u\n WHERE trust_level >= 3 AND (\n :backfill OR u.id IN (:user_ids)\n )\n",
"trigger": 4,
"target_posts": false,
"auto_revoke": true,
"show_posts": false,
"badge_type_id": 2
},
{
"id": 1,
"name": "Basic User",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-user",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 4,
"system": true,
"query": "\n SELECT u.id user_id, current_timestamp granted_at FROM users u\n WHERE trust_level >= 1 AND (\n :backfill OR u.id IN (:user_ids)\n )\n",
"trigger": 4,
"target_posts": false,
"auto_revoke": true,
"show_posts": false,
"badge_type_id": 3
},
{
"id": 2,
"name": "Member",
"description": null,
"grant_count": 0,
"allow_title": false,
"multiple_grant": false,
"icon": "fa-user",
"image": null,
"listable": true,
"enabled": true,
"badge_grouping_id": 4,
"system": true,
"query": "\n SELECT u.id user_id, current_timestamp granted_at FROM users u\n WHERE trust_level >= 2 AND (\n :backfill OR u.id IN (:user_ids)\n )\n",
"trigger": 4,
"target_posts": false,
"auto_revoke": true,
"show_posts": false,
"badge_type_id": 3
}
],
"badge_types": [
{
"id": 3,
"name": "Bronze",
"sort_order": 7
},
{
"id": 1,
"name": "Gold",
"sort_order": 9
},
{
"id": 2,
"name": "Silver",
"sort_order": 8
}
],
"badge_groupings": [
{
"id": 1,
"name": "Getting Started",
"description": null,
"position": 10
},
{
"id": 2,
"name": "Community",
"description": null,
"position": 11
},
{
"id": 3,
"name": "Posting",
"description": null,
"position": 12
},
{
"id": 4,
"name": "Trust Level",
"description": null,
"position": 13
},
{
"id": 5,
"name": "Other",
"description": null,
"position": 14
}
],
"admin_badges": {
"protected_system_fields": [
"badge_type_id",
"multiple_grant",
"target_posts",
"show_posts",
"query",
"trigger",
"auto_revoke",
"listable"
],
"triggers": {
"none": 0,
"post_action": 1,
"post_revision": 2,
"trust_level_change": 4,
"user_change": 8
},
"badge_ids": [
9,
11,
14,
15,
12,
16,
17,
23,
22,
10,
13,
21,
5,
8,
20,
7,
19,
6,
18,
4,
3,
1,
2
],
"badge_grouping_ids": [
1,
2,
3,
4,
5
],
"badge_type_ids": [
1,
2,
3
]
}
}
================================================
FILE: spec/fixtures/categories.json
================================================
{
"featured_users": [],
"category_list": {
"can_create_category": false,
"can_create_topic": false,
"draft": null,
"draft_key": "new_topic",
"draft_sequence": null,
"categories": [
{
"id": 1,
"name": "test1",
"color": "0000FF",
"text_color": "FFFFFF",
"slug": "test1",
"topic_count": 0,
"description": "Test category #1.",
"topic_url": "/t/category-definition-for-test1/1",
"hotness": 5,
"read_restricted": false,
"permission": null,
"post_count": 0,
"topics_week": 0,
"topics_month": 0,
"topics_year": 0,
"description_excerpt": "Test category #1.",
"featured_user_id s": [],
"topics": []
},
{
"id": 2,
"name": "test2",
"color": "00FF00",
"text_color": "FFFFFF",
"slug": "test2",
"topic_count": 0,
"description": "Test category #2.",
"topic_url": "/t/category-definition-for-test2/2",
"hotness": 5,
"read_restricted": false,
"permission": null,
"post_count": 0,
"topics_week": 0,
"topics_month": 0,
"topics_year": 0,
"description_excerpt": "Test category #2.",
"featured_user_ids": [],
"topics": []
},
{
"id": 3,
"name": "test3",
"color": "FF0000",
"text_color": "FFFFFF",
"slug": "test3",
"topic_count": 0,
"description": "Test category #3.",
"topic_url": "/t/category-definition-for-test3/3",
"hotness": 5,
"read_restricted": false,
"permission": null,
"post_count": 0,
"topics_week": 0,
"topics_month": 0,
"topics_year": 0,
"description_excerpt": "Test category #3.",
"featured_user_ids": [],
"topics": []
}
]
}
}
================================================
FILE: spec/fixtures/category_latest_topics.json
================================================
{
"users": [
{
"id": 3,
"username": "fuzzy",
"avatar_template": "//www.gravatar.com/avatar/c0c59575e86a794d733db4ee29b2baf4.png?s={size}&r=pg&d=identicon"
}
],
"topic_list": {
"can_create_topic": true,
"draft": null,
"draft_key": "new_topic",
"draft_sequence": 2,
"topics": [
{
"id": 5,
"title": "About the category",
"fancy_title": "About the category",
"slug": "about-the-category",
"posts_count": 2,
"reply_count": 0,
"highest_post_number": 2,
"image_url": null,
"created_at": "2014-04-16T14:10:50.950-04:00",
"last_posted_at": "2014-04-16T14:31:10.406-04:00",
"bumped": true,
"bumped_at": "2014-04-17T13:35:01.102-04:00",
"unseen": false,
"last_read_post_number": 2,
"unread": 0,
"new_posts": 0,
"pinned": true,
"unpinned": null,
"excerpt": null,
"visible ": true,
"closed": false,
"archived": false,
"views": 0,
"like_count": 0,
"starred": false,
"has_summary": false,
"archetype": "regular",
"last_poster_username": "fuzzy",
"category_id": 2,
"posters": [
{
"extras": "latest",
"description": "Original Poster, Most Recent Poster",
"user_id": 3
}
]
},
{
"id": 4,
"title": "This is a markdown post",
"fancy_title": "This is a markdown post",
"slug": "this-is-a-markdown-post",
"posts_count": 2,
"reply_count": 0,
"highest_post_number": 2,
"image_url": null,
"created_at": "2014-04-16T13:52:58.777-04:00",
"last_posted_at": "2014-04-16T14:31:37.371-04:00",
"bumped": true,
"bumped_at": "2014-04-16T14:31:37.371-04:00",
"unseen": false,
"last_read_post_number": 2,
"unread": 0,
"new_posts": 0,
"pinned": false,
"unpinned ": null,
"visible": true,
"closed": false,
"archived": false,
"views": 0,
"like_count": 0,
"starred": false,
"has_summary": false,
"archetype": "regular",
"last_poster_username": "fuzzy",
"category_id": 2,
"posters": [
{
"extras": "latest",
"description": "Original Poster, Most Recent Poster",
"user_id": 3
}
]
}
]
}
}
================================================
FILE: spec/fixtures/category_topics.json
================================================
{
"users": [
{
"id": 3,
"username": "fuzzy",
"avatar_template": "//www.gravatar.com/avatar/c0c59575e86a794d733db4ee29b2baf4.png?s={size}&r=pg&d=identicon"
}
],
"topic_list": {
"can_create_topic": true,
"draft": null,
"draft_key": "new_topic",
"draft_sequence": 2,
"topics": [
{
"id": 5,
"title": "About the category",
"fancy_title": "About the category",
"slug": "about-the-category",
"posts_count": 2,
"reply_count": 0,
"highest_post_number": 2,
"image_url": null,
"created_at": "2014-04-16T14:10:50.950-04:00",
"last_posted_at": "2014-04-16T14:31:10.406-04:00",
"bumped": true,
"bumped_at": "2014-04-17T13:35:01.102-04:00",
"unseen": false,
"last_read_post_number": 2,
"unread": 0,
"new_posts": 0,
"pinned": true,
"unpinned": null,
"excerpt": null,
"visible ": true,
"closed": false,
"archived": false,
"views": 0,
"like_count": 0,
"starred": false,
"has_summary": false,
"archetype": "regular",
"last_poster_username": "fuzzy",
"category_id": 2,
"posters": [
{
"extras": "latest",
"description": "Original Poster, Most Recent Poster",
"user_id": 3
}
]
},
{
"id": 4,
"title": "This is a markdown post",
"fancy_title": "This is a markdown post",
"slug": "this-is-a-markdown-post",
"posts_count": 2,
"reply_count": 0,
"highest_post_number": 2,
"image_url": null,
"created_at": "2014-04-16T13:52:58.777-04:00",
"last_posted_at": "2014-04-16T14:31:37.371-04:00",
"bumped": true,
"bumped_at": "2014-04-16T14:31:37.371-04:00",
"unseen": false,
"last_read_post_number": 2,
"unread": 0,
"new_posts": 0,
"pinned": false,
"unpinned ": null,
"visible": true,
"closed": false,
"archived": false,
"views": 0,
"like_count": 0,
"starred": false,
"has_summary": false,
"archetype": "regular",
"last_poster_username": "fuzzy",
"category_id": 2,
"posters": [
{
"extras": "latest",
"description": "Original Poster, Most Recent Poster",
"user_id": 3
}
]
}
]
}
}
================================================
FILE: spec/fixtures/create_topic_with_tags.json
================================================
{
"id": 29,
"name": null,
"username": "blake",
"avatar_template": "/user_avatar/localhost/blake/{size}/3_2.png",
"created_at": "2020-07-09T15:52:43.802Z",
"cooked": "<p>cb8ee24eb3c5a81139958cf2b6bee9bd 2a7f7ea218e24c9ee6528cd383527f99 5a3006b92918946230385393a83b6005</p>",
"post_number": 1,
"post_type": 1,
"updated_at": "2020-07-09T15:52:43.802Z",
"reply_count": 0,
"reply_to_post_number": null,
"quote_count": 0,
"incoming_link_count": 0,
"reads": 0,
"readers_count": 0,
"score": 0,
"yours": true,
"topic_id": 21,
"topic_slug": "1412a036bfe-6480718525e-9f71f98ef46",
"display_username": null,
"primary_group_name": null,
"primary_group_flair_url": null,
"primary_group_flair_bg_color": null,
"primary_group_flair_color": null,
"version": 1,
"can_edit": true,
"can_delete": false,
"can_recover": false,
"can_wiki": true,
"user_title": null,
"actions_summary": [
{
"id": 3,
"can_act": true
},
{
"id": 4,
"can_act": true
},
{
"id": 8,
"can_act": true
},
{
"id": 7,
"can_act": true
}
],
"moderator": false,
"admin": true,
"staff": true,
"user_id": 1,
"draft_sequence": 0,
"hidden": false,
"trust_level": 1,
"deleted_at": null,
"user_deleted": false,
"edit_reason": null,
"can_view_edit_history": true,
"wiki": false,
"reviewable_id": null,
"reviewable_score_count": 0,
"reviewable_score_pending_count": 0
}
================================================
FILE: spec/fixtures/email_list_all.json
================================================
[
{
"id": 64,
"reply_key": null,
"to_address": "steve7@example.com",
"email_type": "signup",
"user_id": 22,
"created_at": "2015-01-03T13:33:46.730Z",
"skipped": false,
"user": {
"id": 22,
"username": "steve7",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve7/{size}/2.png"
}
},
{
"id": 63,
"reply_key": null,
"to_address": "steve6@example.com",
"email_type": "user_private_message",
"user_id": 21,
"created_at": "2015-01-02T17:18:47.098Z",
"skipped": false,
"user": {
"id": 21,
"username": "steve6",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve6/{size}/2.png"
}
},
{
"id": 62,
"reply_key": null,
"to_address": "steve5@example.com",
"email_type": "user_private_message",
"user_id": 20,
"created_at": "2015-01-02T16:05:38.192Z",
"skipped": false,
"user": {
"id": 20,
"username": "steve5",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve5/{size}/2.png"
}
},
{
"id": 61,
"reply_key": null,
"to_address": "steve4@example.com",
"email_type": "user_private_message",
"user_id": 19,
"created_at": "2015-01-02T15:59:27.137Z",
"skipped": false,
"user": {
"id": 19,
"username": "steve4",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve4/{size}/2.png"
}
},
{
"id": 60,
"reply_key": null,
"to_address": "steve5@example.com",
"email_type": "signup",
"user_id": 20,
"created_at": "2015-01-02T15:57:57.061Z",
"skipped": false,
"user": {
"id": 20,
"username": "steve5",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve5/{size}/2.png"
}
},
{
"id": 59,
"reply_key": null,
"to_address": "steve4@example.com",
"email_type": "signup",
"user_id": 19,
"created_at": "2015-01-02T15:52:36.210Z",
"skipped": false,
"user": {
"id": 19,
"username": "steve4",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve4/{size}/2.png"
}
},
{
"id": 58,
"reply_key": null,
"to_address": "steve3@example.com",
"email_type": "signup",
"user_id": 18,
"created_at": "2015-01-02T15:50:29.330Z",
"skipped": false,
"user": {
"id": 18,
"username": "steve3",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve3/{size}/2.png"
}
},
{
"id": 57,
"reply_key": null,
"to_address": "steve3@example.com",
"email_type": "user_private_message",
"user_id": 18,
"created_at": "2015-01-02T14:07:40.357Z",
"skipped": true,
"skipped_reason": "The notification this email is about has already been read",
"user": {
"id": 18,
"username": "steve3",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve3/{size}/2.png"
}
},
{
"id": 56,
"reply_key": null,
"to_address": "steve3@example.com",
"email_type": "signup",
"user_id": 18,
"created_at": "2015-01-02T13:53:04.352Z",
"skipped": false,
"user": {
"id": 18,
"username": "steve3",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve3/{size}/2.png"
}
},
{
"id": 55,
"reply_key": null,
"to_address": "steve2@example.com",
"email_type": "signup",
"user_id": 17,
"created_at": "2015-01-02T13:39:40.455Z",
"skipped": false,
"user": {
"id": 17,
"username": "steve2",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve2/{size}/2.png"
}
},
{
"id": 54,
"reply_key": null,
"to_address": "batman@gotham.com",
"email_type": "signup",
"user_id": 1,
"created_at": "2015-01-02T13:38:11.418Z",
"skipped": false,
"user": {
"id": 1,
"username": "test_user",
"uploaded_avatar_id": 7,
"avatar_template": "/user_avatar/localhost/test_user/{size}/7.png"
}
},
{
"id": 53,
"reply_key": null,
"to_address": "dave3@gotham.com",
"email_type": "signup",
"user_id": 10,
"created_at": "2015-01-01T13:08:57.914Z",
"skipped": false,
"user": {
"id": 10,
"username": "dave3",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/dave3/{size}/2.png"
}
},
{
"id": 52,
"reply_key": null,
"to_address": "dave2@gotham.com",
"email_type": "authorize_email",
"user_id": 9,
"created_at": "2015-01-01T01:22:45.479Z",
"skipped": false,
"user": {
"id": 9,
"username": "dave2",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/dave2/{size}/2.png"
}
},
{
"id": 51,
"reply_key": null,
"to_address": "phil@example.com",
"email_type": "user_private_message",
"user_id": 7,
"created_at": "2015-01-01T01:01:33.316Z",
"skipped": false,
"user": {
"id": 7,
"username": "phil12",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/phil12/{size}/2.png"
}
},
{
"id": 50,
"reply_key": null,
"to_address": "dave@example.com",
"email_type": "user_private_message",
"user_id": 8,
"created_at": "2015-01-01T01:01:33.222Z",
"skipped": false,
"user": {
"id": 8,
"username": "dave",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/dave/{size}/2.png"
}
},
{
"id": 49,
"reply_key": null,
"to_address": "steve@example.com",
"email_type": "user_private_message",
"user_id": 4,
"created_at": "2015-01-01T01:01:33.200Z",
"skipped": false,
"user": {
"id": 4,
"username": "steve",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve/{size}/2.png"
}
},
{
"id": 48,
"reply_key": null,
"to_address": "steve@example.com",
"email_type": "signup",
"user_id": 4,
"created_at": "2015-01-01T01:01:33.141Z",
"skipped": false,
"user": {
"id": 4,
"username": "steve",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve/{size}/2.png"
}
},
{
"id": 47,
"reply_key": null,
"to_address": "steve999@example.com",
"email_type": "authorize_email",
"user_id": 4,
"created_at": "2015-01-01T01:01:32.846Z",
"skipped": false,
"user": {
"id": 4,
"username": "steve",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve/{size}/2.png"
}
},
{
"id": 46,
"reply_key": null,
"to_address": "steve345@example.com",
"email_type": "authorize_email",
"user_id": 4,
"created_at": "2015-01-01T01:01:32.844Z",
"skipped": false,
"user": {
"id": 4,
"username": "steve",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/steve/{size}/2.png"
}
},
{
"id": 45,
"reply_key": null,
"to_address": "robin3@gotham.com",
"email_type": "authorize_email",
"user_id": 2,
"created_at": "2015-01-01T01:01:24.446Z",
"skipped": false,
"user": {
"id": 2,
"username": "robin",
"uploaded_avatar_id": 3,
"avatar_template": "/user_avatar/localhost/robin/{size}/3.png"
}
},
{
"id": 44,
"reply_key": null,
"to_address": "robin3@gotham.com",
"email_type": "authorize_email",
"user_id": 2,
"created_at": "2015-01-01T01:01:24.445Z",
"skipped": false,
"user": {
"id": 2,
"username": "robin",
"uploaded_avatar_id": 3,
"avatar_template": "/user_avatar/localhost/robin/{size}/3.png"
}
},
{
"id": 43,
"reply_key": null,
"to_address": "robin3@gotham.com",
"email_type": "authorize_email",
"user_id": 2,
"created_at": "2015-01-01T01:01:24.444Z",
"skipped": false,
"user": {
"id": 2,
"username": "robin",
"uploaded_avatar_id": 3,
"avatar_template": "/user_avatar/localhost/robin/{size}/3.png"
}
},
{
"id": 42,
"reply_key": null,
"to_address": "robin3@gotham.com",
"email_type": "authorize_email",
"user_id": 2,
"created_at": "2015-01-01T01:01:24.441Z",
"skipped": false,
"user": {
"id": 2,
"username": "robin",
"uploaded_avatar_id": 3,
"avatar_template": "/user_avatar/localhost/robin/{size}/3.png"
}
},
{
"id": 41,
"reply_key": null,
"to_address": "no_email_found",
"email_type": "signup",
"user_id": null,
"created_at": "2014-12-31T18:12:05.707Z",
"skipped": true,
"skipped_reason": "Can't find user with id 6",
"user": null
},
{
"id": 40,
"reply_key": null,
"to_address": "foo@bar.com",
"email_type": "authorize_email",
"user_id": 8,
"created_at": "2014-12-31T17:56:39.302Z",
"skipped": false,
"user": {
"id": 8,
"username": "dave",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/dave/{size}/2.png"
}
},
{
"id": 39,
"reply_key": null,
"to_address": "dave2@example.com",
"email_type": "user_private_message",
"user_id": 9,
"created_at": "2014-12-31T17:56:21.004Z",
"skipped": false,
"user": {
"id": 9,
"username": "dave2",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/dave2/{size}/2.png"
}
},
{
"id": 38,
"reply_key": null,
"to_address": "fake@example.com",
"email_type": "signup",
"user_id": 16,
"created_at": "2014-12-31T17:54:32.454Z",
"skipped": false,
"user": {
"id": 16,
"username": "carl",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/carl/{size}/2.png"
}
},
{
"id": 37,
"reply_key": null,
"to_address": "test_user2@example.com",
"email_type": "authorize_email",
"user_id": 1,
"created_at": "2014-12-31T17:53:48.162Z",
"skipped": false,
"user": {
"id": 1,
"username": "test_user",
"uploaded_avatar_id": 7,
"avatar_template": "/user_avatar/localhost/test_user/{size}/7.png"
}
},
{
"id": 36,
"reply_key": null,
"to_address": "foo@bar.com",
"email_type": "authorize_email",
"user_id": 7,
"created_at": "2014-12-31T17:13:15.797Z",
"skipped": false,
"user": {
"id": 7,
"username": "phil12",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/phil12/{size}/2.png"
}
},
{
"id": 35,
"reply_key": null,
"to_address": "batman@gotham.com",
"email_type": "authorize_email",
"user_id": 7,
"created_at": "2014-12-31T17:13:06.259Z",
"skipped": false,
"user": {
"id": 7,
"username": "phil12",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/phil12/{size}/2.png"
}
},
{
"id": 34,
"reply_key": null,
"to_address": "dave7@example.com",
"email_type": "signup",
"user_id": 14,
"created_at": "2014-12-31T17:12:43.031Z",
"skipped": false,
"user": {
"id": 14,
"username": "dave7",
"uploaded_avatar_id": null,
"avatar_template": "/letter_avatar/dave7/{size}/2.png"
}
},
{
"id": 33,
"reply_key": null,
"to_address": "batman@gotham.com",
"email_type": "authorize_email",
"user_id": 2,
"created_at": "2014-
gitextract_ygzwukcj/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .rubocop.yml
├── .streerc
├── CHANGELOG.md
├── Gemfile
├── Guardfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── config_example.yml
├── discourse_api.gemspec
├── examples/
│ ├── backups.rb
│ ├── badges.rb
│ ├── bookmark_topic.rb
│ ├── category.rb
│ ├── change_topic_status.rb
│ ├── create_private_message.rb
│ ├── create_topic.rb
│ ├── create_update_category.rb
│ ├── create_user.rb
│ ├── dashboard.rb
│ ├── disposable_invite_tokens.rb
│ ├── example.rb
│ ├── group_set_user_notification_level.rb
│ ├── groups.rb
│ ├── invite_users.rb
│ ├── manage_api_keys.rb
│ ├── notifications.rb
│ ├── polls.rb
│ ├── post_action.rb
│ ├── search.rb
│ ├── sent_private_messages.rb
│ ├── sso.rb
│ ├── topic_lists.rb
│ ├── update_user.rb
│ └── upload_file.rb
├── lib/
│ ├── discourse_api/
│ │ ├── api/
│ │ │ ├── api_key.rb
│ │ │ ├── backups.rb
│ │ │ ├── badges.rb
│ │ │ ├── categories.rb
│ │ │ ├── dashboard.rb
│ │ │ ├── email.rb
│ │ │ ├── groups.rb
│ │ │ ├── invite.rb
│ │ │ ├── notifications.rb
│ │ │ ├── params.rb
│ │ │ ├── polls.rb
│ │ │ ├── posts.rb
│ │ │ ├── private_messages.rb
│ │ │ ├── search.rb
│ │ │ ├── site_settings.rb
│ │ │ ├── sso.rb
│ │ │ ├── tags.rb
│ │ │ ├── topics.rb
│ │ │ ├── uploads.rb
│ │ │ ├── user_actions.rb
│ │ │ └── users.rb
│ │ ├── client.rb
│ │ ├── error.rb
│ │ ├── example_helper.rb
│ │ ├── single_sign_on.rb
│ │ └── version.rb
│ └── discourse_api.rb
└── spec/
├── discourse_api/
│ ├── api/
│ │ ├── api_key_spec.rb
│ │ ├── backups_spec.rb
│ │ ├── badges_spec.rb
│ │ ├── categories_spec.rb
│ │ ├── email_spec.rb
│ │ ├── groups_spec.rb
│ │ ├── invite_spec.rb
│ │ ├── notifications_spec.rb
│ │ ├── params_spec.rb
│ │ ├── polls_spec.rb
│ │ ├── posts_spec.rb
│ │ ├── private_messages_spec.rb
│ │ ├── search_spec.rb
│ │ ├── site_settings_spec.rb
│ │ ├── sso_spec.rb
│ │ ├── topics_spec.rb
│ │ ├── uploads_spec.rb
│ │ ├── user_actions_spec.rb
│ │ └── users_spec.rb
│ ├── client_spec.rb
│ └── single_sign_on_spec.rb
├── fixtures/
│ ├── admin_user.json
│ ├── api_key.json
│ ├── backups.json
│ ├── badges.json
│ ├── categories.json
│ ├── category_latest_topics.json
│ ├── category_topics.json
│ ├── create_topic_with_tags.json
│ ├── email_list_all.json
│ ├── email_settings.json
│ ├── group.json
│ ├── groups.json
│ ├── hot.json
│ ├── latest.json
│ ├── list_api_keys.json
│ ├── members_0.json
│ ├── members_1.json
│ ├── members_2.json
│ ├── new.json
│ ├── notification_success.json
│ ├── notifications.json
│ ├── polls_toggle_status.json
│ ├── polls_vote.json
│ ├── polls_voters.json
│ ├── post.json
│ ├── post_action_users.json
│ ├── posts_before.json
│ ├── posts_latest.json
│ ├── private_messages.json
│ ├── regenerate_api_key.json
│ ├── replies.json
│ ├── replies_and_topics.json
│ ├── retrieve_invite.json
│ ├── search.json
│ ├── top.json
│ ├── topic.json
│ ├── topic_invite_user.json
│ ├── topic_posts.json
│ ├── topics_created_by.json
│ ├── update_trust_level.json
│ ├── upload_avatar.json
│ ├── upload_file.json
│ ├── user.json
│ ├── user_activate_success.json
│ ├── user_badges.json
│ ├── user_create_success.json
│ ├── user_grant_admin.json
│ ├── user_grant_moderator.json
│ ├── user_list.json
│ ├── user_log_out_success.json
│ ├── user_update_avatar_success.json
│ ├── user_update_user.json
│ └── user_update_username.json
└── spec_helper.rb
SYMBOL INDEX (249 symbols across 28 files)
FILE: lib/discourse_api/api/api_key.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type ApiKey (line 4) | module ApiKey
function list_api_keys (line 5) | def list_api_keys
function create_api_key (line 10) | def create_api_key(args)
function revoke_api_key (line 15) | def revoke_api_key(id)
function undo_revoke_api_key (line 19) | def undo_revoke_api_key(id)
function delete_api_key (line 23) | def delete_api_key(id)
FILE: lib/discourse_api/api/backups.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Backups (line 4) | module Backups
function backups (line 5) | def backups
function create_backup (line 10) | def create_backup
function restore_backup (line 14) | def restore_backup(file_name)
function download_backup (line 18) | def download_backup(file_name, destination)
FILE: lib/discourse_api/api/badges.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Badges (line 4) | module Badges
function badges (line 5) | def badges
function user_badges (line 10) | def user_badges(username)
function grant_user_badge (line 15) | def grant_user_badge(params = {})
function create_badge (line 19) | def create_badge(params = {})
FILE: lib/discourse_api/api/categories.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Categories (line 4) | module Categories
function create_category (line 8) | def create_category(args = {})
function update_category (line 14) | def update_category(args = {})
function reorder_categories (line 21) | def reorder_categories(args = {})
function delete_category (line 26) | def delete_category(id)
function categories (line 31) | def categories(params = {})
function categories_full (line 35) | def categories_full(params = {})
function category_latest_topics (line 40) | def category_latest_topics(args = {})
function category_latest_topics_full (line 49) | def category_latest_topics_full(args = {})
function category_top_topics (line 57) | def category_top_topics(category_slug)
function category_top_topics_full (line 66) | def category_top_topics_full(category_slug)
function category_new_topics (line 71) | def category_new_topics(category_slug)
function category_new_topics_full (line 76) | def category_new_topics_full(category_slug)
function category (line 81) | def category(id)
function category_set_user_notification (line 87) | def category_set_user_notification(args = {})
function category_set_user_notification_level (line 93) | def category_set_user_notification_level(category_id, params)
function common_category_params (line 100) | def common_category_params(args, include_id: false)
FILE: lib/discourse_api/api/dashboard.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Dashboard (line 4) | module Dashboard
function get_dashboard_stats (line 5) | def get_dashboard_stats
function get_dashboard_stats_totals (line 10) | def get_dashboard_stats_totals
FILE: lib/discourse_api/api/email.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Email (line 4) | module Email
function email_settings (line 5) | def email_settings
function list_email (line 10) | def list_email(filter)
FILE: lib/discourse_api/api/groups.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Groups (line 4) | module Groups
function create_group (line 5) | def create_group(args)
function update_group (line 38) | def update_group(group_id, args)
function group_add_owners (line 68) | def group_add_owners(group_id, args)
function group_remove_owners (line 73) | def group_remove_owners(group_id, args)
function groups (line 78) | def groups(args = {})
function group (line 87) | def group(group_name)
function group_add (line 92) | def group_add(group_id, users)
function group_remove (line 107) | def group_remove(group_id, users)
function delete_group (line 122) | def delete_group(group_id)
function group_members (line 126) | def group_members(group_name, params = {})
function group_set_user_notification_level (line 138) | def group_set_user_notification_level(group, user_id, notification...
FILE: lib/discourse_api/api/invite.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Invite (line 4) | module Invite
function invite_user (line 5) | def invite_user(params = {})
function invite_user_to_topic (line 24) | def invite_user_to_topic(params = {})
function invite_to_topic (line 29) | def invite_to_topic(topic_id, params = {})
function retrieve_invite (line 35) | def retrieve_invite(params = {})
function disposable_tokens (line 44) | def disposable_tokens(params = {})
function update_invite (line 48) | def update_invite(invite_id, params = {})
function destroy_all_expired_invites (line 67) | def destroy_all_expired_invites
function resend_all_invites (line 71) | def resend_all_invites
function resend_invite (line 75) | def resend_invite(email)
function destroy_invite (line 79) | def destroy_invite(invite_id)
FILE: lib/discourse_api/api/notifications.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Notifications (line 4) | module Notifications
function notifications (line 5) | def notifications(params = {})
FILE: lib/discourse_api/api/params.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
function params (line 4) | def self.params(args)
class Params (line 8) | class Params
method initialize (line 9) | def initialize(args)
method required (line 17) | def required(*keys)
method optional (line 25) | def optional(*keys)
method default (line 30) | def default(args)
method to_h (line 35) | def to_h
FILE: lib/discourse_api/api/polls.rb
type DiscourseApi (line 3) | module DiscourseApi
type API (line 4) | module API
type Polls (line 5) | module Polls
function poll_vote (line 6) | def poll_vote(args)
function toggle_poll_status (line 11) | def toggle_poll_status(args)
function poll_voters (line 21) | def poll_voters(args)
FILE: lib/discourse_api/api/posts.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Posts (line 4) | module Posts
function create_post (line 5) | def create_post(args)
function create_post_action (line 10) | def create_post_action(args)
function get_post (line 15) | def get_post(id, args = {})
function posts (line 21) | def posts(args = {})
function wikify_post (line 27) | def wikify_post(id)
function edit_post (line 31) | def edit_post(id, raw)
function delete_post (line 35) | def delete_post(id)
function destroy_post_action (line 39) | def destroy_post_action(post_id, post_action_type_id)
function post_action_users (line 43) | def post_action_users(post_id, post_action_type_id)
FILE: lib/discourse_api/api/private_messages.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type PrivateMessages (line 4) | module PrivateMessages
function create_private_message (line 6) | def create_private_message(args = {})
function create_pm (line 15) | def create_pm(args = {})
function private_messages (line 25) | def private_messages(username, *args)
function sent_private_messages (line 30) | def sent_private_messages(username, *args)
FILE: lib/discourse_api/api/search.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Search (line 4) | module Search
function search (line 11) | def search(term, options = {})
FILE: lib/discourse_api/api/site_settings.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type SiteSettings (line 4) | module SiteSettings
function site_setting_update (line 5) | def site_setting_update(args = {})
FILE: lib/discourse_api/api/sso.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type SSO (line 4) | module SSO
function sync_sso (line 5) | def sync_sso(params = {})
FILE: lib/discourse_api/api/tags.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Tags (line 4) | module Tags
function show_tag (line 5) | def show_tag(tag)
FILE: lib/discourse_api/api/topics.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Topics (line 4) | module Topics
function create_topic (line 9) | def create_topic(args = {})
function create_topic_action (line 27) | def create_topic_action(args)
function edit_topic_timestamp (line 33) | def edit_topic_timestamp(topic_id, timestamp)
function latest_topics (line 37) | def latest_topics(params = {})
function top_topics (line 42) | def top_topics(params = {})
function new_topics (line 47) | def new_topics(params = {})
function rename_topic (line 52) | def rename_topic(topic_id, title)
function recategorize_topic (line 56) | def recategorize_topic(topic_id, category_id)
function change_topic_status (line 61) | def change_topic_status(topic_slug, topic_id, params = {})
function update_topic_status (line 66) | def update_topic_status(topic_id, params = {})
function topic (line 71) | def topic(id, params = {})
function topics_by (line 76) | def topics_by(username, params = {})
function delete_topic (line 81) | def delete_topic(id)
function topic_posts (line 85) | def topic_posts(topic_id, post_ids = [], params = {})
function change_owner (line 105) | def change_owner(topic_id, params = {})
function topic_set_user_notification_level (line 111) | def topic_set_user_notification_level(topic_id, params)
function bookmark_topic (line 116) | def bookmark_topic(topic_id)
function remove_topic_bookmark (line 120) | def remove_topic_bookmark(topic_id)
function get_topic_url_by_external_id (line 124) | def get_topic_url_by_external_id(external_id)
FILE: lib/discourse_api/api/uploads.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Uploads (line 4) | module Uploads
function upload_file (line 5) | def upload_file(args)
FILE: lib/discourse_api/api/user_actions.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type UserActions (line 4) | module UserActions
function user_replies (line 5) | def user_replies(username)
function user_topics_and_replies (line 11) | def user_topics_and_replies(username)
FILE: lib/discourse_api/api/users.rb
type DiscourseApi (line 2) | module DiscourseApi
type API (line 3) | module API
type Users (line 4) | module Users
function activate (line 5) | def activate(id)
function user (line 9) | def user(username, params = {})
function user_sso (line 14) | def user_sso(user_id)
function update_avatar (line 19) | def update_avatar(username, args)
function update_email (line 26) | def update_email(username, email)
function update_user (line 30) | def update_user(username, args)
function update_username (line 52) | def update_username(username, new_username)
function update_trust_level (line 56) | def update_trust_level(user_id, args)
function create_user (line 62) | def create_user(args)
function log_out (line 72) | def log_out(id)
function list_users (line 76) | def list_users(type, params = {})
function grant_admin (line 81) | def grant_admin(user_id)
function revoke_admin (line 86) | def revoke_admin(user_id)
function grant_moderation (line 91) | def grant_moderation(user_id)
function revoke_moderation (line 96) | def revoke_moderation(user_id)
function by_external_id (line 100) | def by_external_id(external_id)
function suspend (line 105) | def suspend(user_id, suspend_until, reason)
function unsuspend (line 109) | def unsuspend(user_id)
function anonymize (line 113) | def anonymize(user_id)
function delete_user (line 117) | def delete_user(user_id, delete_posts = false)
function check_username (line 121) | def check_username(username)
function deactivate (line 126) | def deactivate(user_id)
FILE: lib/discourse_api/client.rb
type DiscourseApi (line 29) | module DiscourseApi
class Client (line 30) | class Client
method initialize (line 58) | def initialize(host, api_key = nil, api_username = nil)
method timeout= (line 66) | def timeout=(timeout)
method api_username= (line 71) | def api_username=(api_username)
method connection_options (line 76) | def connection_options
method ssl (line 89) | def ssl(options)
method delete (line 93) | def delete(path, params = {})
method get (line 97) | def get(path, params = {})
method post (line 101) | def post(path, params = {})
method put (line 111) | def put(path, params = {})
method patch (line 115) | def patch(path, params = {})
method user_agent (line 119) | def user_agent
method deprecated (line 123) | def deprecated(old, new)
method connection (line 129) | def connection
method request (line 163) | def request(method, path, params = {})
method handle_error (line 177) | def handle_error(response)
method check_subdirectory (line 192) | def check_subdirectory(host)
FILE: lib/discourse_api/error.rb
type DiscourseApi (line 2) | module DiscourseApi
class DiscourseError (line 3) | class DiscourseError < StandardError
method initialize (line 6) | def initialize(message, response = nil)
class Error (line 12) | class Error < DiscourseError
method initialize (line 19) | def initialize(exception = $!)
class UnauthenticatedError (line 25) | class UnauthenticatedError < DiscourseError
class NotFoundError (line 28) | class NotFoundError < DiscourseError
class UnprocessableEntity (line 31) | class UnprocessableEntity < DiscourseError
class TooManyRequests (line 34) | class TooManyRequests < DiscourseError
class Timeout (line 37) | class Timeout < Error
FILE: lib/discourse_api/example_helper.rb
type DiscourseApi (line 4) | module DiscourseApi
class ExampleHelper (line 5) | class ExampleHelper
method load_yml (line 6) | def self.load_yml
FILE: lib/discourse_api/single_sign_on.rb
type DiscourseApi (line 6) | module DiscourseApi
class SingleSignOn (line 7) | class SingleSignOn
class ParseError (line 8) | class ParseError < RuntimeError
class MissingConfigError (line 10) | class MissingConfigError < RuntimeError
method sso_secret (line 59) | def self.sso_secret
method sso_url (line 63) | def self.sso_url
method parse_hash (line 67) | def self.parse_hash(payload)
method parse (line 96) | def self.parse(payload, sso_secret = nil)
method diagnostics (line 132) | def diagnostics
method sso_secret (line 136) | def sso_secret
method sso_url (line 140) | def sso_url
method custom_fields (line 144) | def custom_fields
method sign (line 148) | def sign(payload)
method to_url (line 152) | def to_url(base_url = nil)
method payload (line 157) | def payload
method unsigned_payload (line 162) | def unsigned_payload
FILE: lib/discourse_api/version.rb
type DiscourseApi (line 2) | module DiscourseApi
FILE: spec/discourse_api/api/params_spec.rb
function params_for (line 5) | def params_for(h)
FILE: spec/spec_helper.rb
function host (line 22) | def host
function a_delete (line 26) | def a_delete(path)
function a_get (line 30) | def a_get(path)
function a_post (line 34) | def a_post(path)
function a_put (line 38) | def a_put(path)
function stub_delete (line 42) | def stub_delete(path)
function stub_get (line 46) | def stub_get(path)
function stub_post (line 50) | def stub_post(path)
function stub_put (line 54) | def stub_put(path)
function fixture_path (line 58) | def fixture_path
function fixture (line 62) | def fixture(file)
function escape_params (line 66) | def escape_params(params)
Condensed preview — 139 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (350K chars).
[
{
"path": ".github/workflows/ci.yml",
"chars": 1289,
"preview": "name: CI\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n lint:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": ".gitignore",
"chars": 34,
"preview": "Gemfile.lock\ncoverage\n/config.yml\n"
},
{
"path": ".rubocop.yml",
"chars": 88,
"preview": "inherit_gem:\n rubocop-discourse: stree-compat.yml\n\nDiscourse/Plugins:\n Enabled: false\n"
},
{
"path": ".streerc",
"chars": 78,
"preview": "--print-width=100\n--plugins=plugin/trailing_comma,plugin/disable_auto_ternary\n"
},
{
"path": "CHANGELOG.md",
"chars": 9606,
"preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
},
{
"path": "Gemfile",
"chars": 128,
"preview": "# frozen_string_literal: true\nsource \"https://rubygems.org\"\n\n# Specify your gem's dependencies in discourse_api.gemspec\n"
},
{
"path": "Guardfile",
"chars": 192,
"preview": "# frozen_string_literal: true\nguard :rspec do\n watch(%r{^spec/.+_spec\\.rb$})\n watch(%r{^lib/(.+)\\.rb$}) { |m| \"spe"
},
{
"path": "LICENSE.txt",
"chars": 1099,
"preview": "Copyright (c) 2014 Civilized Discourse Construction Kit, Inc.\n\nMIT License\n\nPermission is hereby granted, free of charge"
},
{
"path": "README.md",
"chars": 3370,
"preview": "# DiscourseApi\n\nThe Discourse API gem allows you to consume the Discourse API\n\n## Installation\n\nAdd this line to your ap"
},
{
"path": "Rakefile",
"chars": 251,
"preview": "# frozen_string_literal: true\nrequire 'bundler/gem_tasks'\n\nrequire 'rspec/core/rake_task'\nRSpec::Core::RakeTask.new(:spe"
},
{
"path": "config_example.yml",
"chars": 517,
"preview": "# Specify your environment by making a copy of this file to create a file in your root directory called config.yml with "
},
{
"path": "discourse_api.gemspec",
"chars": 1710,
"preview": "# frozen_string_literal: true\n\nlib = File.expand_path(\"lib\", __dir__)\n$LOAD_PATH.unshift(lib) if !$LOAD_PATH.include?(li"
},
{
"path": "examples/backups.rb",
"chars": 643,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/badges.rb",
"chars": 1000,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/bookmark_topic.rb",
"chars": 514,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/category.rb",
"chars": 1081,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/change_topic_status.rb",
"chars": 1266,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/create_private_message.rb",
"chars": 559,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/create_topic.rb",
"chars": 1004,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/create_update_category.rb",
"chars": 1091,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/create_user.rb",
"chars": 600,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/dashboard.rb",
"chars": 601,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/disposable_invite_tokens.rb",
"chars": 782,
"preview": "# frozen_string_literal: true\n# requires this plugin => https://github.com/discourse/discourse-invite-tokens\n\nrequire \"c"
},
{
"path": "examples/example.rb",
"chars": 440,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/group_set_user_notification_level.rb",
"chars": 1810,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/groups.rb",
"chars": 936,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/invite_users.rb",
"chars": 1056,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/manage_api_keys.rb",
"chars": 1263,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/notifications.rb",
"chars": 644,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/polls.rb",
"chars": 811,
"preview": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../."
},
{
"path": "examples/post_action.rb",
"chars": 798,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/search.rb",
"chars": 444,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/sent_private_messages.rb",
"chars": 436,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/sso.rb",
"chars": 545,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/topic_lists.rb",
"chars": 618,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/update_user.rb",
"chars": 1294,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "examples/upload_file.rb",
"chars": 592,
"preview": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../.."
},
{
"path": "lib/discourse_api/api/api_key.rb",
"chars": 591,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module ApiKey\n def list_api_keys\n respons"
},
{
"path": "lib/discourse_api/api/backups.rb",
"chars": 605,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Backups\n def backups\n response = g"
},
{
"path": "lib/discourse_api/api/badges.rb",
"chars": 1015,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Badges\n def badges\n response = get"
},
{
"path": "lib/discourse_api/api/categories.rb",
"chars": 4084,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Categories\n # :color and :text_color are "
},
{
"path": "lib/discourse_api/api/dashboard.rb",
"chars": 544,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Dashboard\n def get_dashboard_stats\n "
},
{
"path": "lib/discourse_api/api/email.rb",
"chars": 315,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Email\n def email_settings\n respons"
},
{
"path": "lib/discourse_api/api/groups.rb",
"chars": 4362,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Groups\n def create_group(args)\n ar"
},
{
"path": "lib/discourse_api/api/invite.rb",
"chars": 2051,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Invite\n def invite_user(params = {})\n "
},
{
"path": "lib/discourse_api/api/notifications.rb",
"chars": 323,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Notifications\n def notifications(params ="
},
{
"path": "lib/discourse_api/api/params.rb",
"chars": 1018,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n def self.params(args)\n Params.new(args)\n end\n"
},
{
"path": "lib/discourse_api/api/polls.rb",
"chars": 726,
"preview": "# frozen_string_literal: true\n\nmodule DiscourseApi\n module API\n module Polls\n def poll_vote(args)\n args "
},
{
"path": "lib/discourse_api/api/posts.rb",
"chars": 1379,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Posts\n def create_post(args)\n args"
},
{
"path": "lib/discourse_api/api/private_messages.rb",
"chars": 1187,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module PrivateMessages\n # TODO: Deprecated. Remo"
},
{
"path": "lib/discourse_api/api/search.rb",
"chars": 722,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Search\n # Returns search results that mat"
},
{
"path": "lib/discourse_api/api/site_settings.rb",
"chars": 354,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module SiteSettings\n def site_setting_update(arg"
},
{
"path": "lib/discourse_api/api/sso.rb",
"chars": 250,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module SSO\n def sync_sso(params = {})\n ss"
},
{
"path": "lib/discourse_api/api/tags.rb",
"chars": 193,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Tags\n def show_tag(tag)\n response "
},
{
"path": "lib/discourse_api/api/topics.rb",
"chars": 3735,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Topics\n # :category OPTIONAL name of cate"
},
{
"path": "lib/discourse_api/api/uploads.rb",
"chars": 345,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Uploads\n def upload_file(args)\n ar"
},
{
"path": "lib/discourse_api/api/user_actions.rb",
"chars": 492,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module UserActions\n def user_replies(username)\n "
},
{
"path": "lib/discourse_api/api/users.rb",
"chars": 3578,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n module API\n module Users\n def activate(id)\n put(\"/adm"
},
{
"path": "lib/discourse_api/client.rb",
"chars": 5691,
"preview": "# frozen_string_literal: true\nrequire \"faraday\"\nrequire \"faraday/follow_redirects\"\nrequire \"faraday/multipart\"\nrequire \""
},
{
"path": "lib/discourse_api/error.rb",
"chars": 821,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n class DiscourseError < StandardError\n attr_reader :response\n\n "
},
{
"path": "lib/discourse_api/example_helper.rb",
"chars": 344,
"preview": "# frozen_string_literal: true\nrequire \"yaml\"\n\nmodule DiscourseApi\n class ExampleHelper\n def self.load_yml\n conf"
},
{
"path": "lib/discourse_api/single_sign_on.rb",
"chars": 4496,
"preview": "# frozen_string_literal: true\nrequire \"base64\"\nrequire \"rack\"\nrequire \"openssl\"\n\nmodule DiscourseApi\n class SingleSignO"
},
{
"path": "lib/discourse_api/version.rb",
"chars": 74,
"preview": "# frozen_string_literal: true\nmodule DiscourseApi\n VERSION = \"2.1.0\"\nend\n"
},
{
"path": "lib/discourse_api.rb",
"chars": 236,
"preview": "# frozen_string_literal: true\nrequire \"discourse_api/api/params\"\nrequire \"discourse_api/client\"\nrequire \"discourse_api/e"
},
{
"path": "spec/discourse_api/api/api_key_spec.rb",
"chars": 3163,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::ApiKey do\n subject(:client) { Discours"
},
{
"path": "spec/discourse_api/api/backups_spec.rb",
"chars": 771,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Backups do\n subject(:client) { Discour"
},
{
"path": "spec/discourse_api/api/badges_spec.rb",
"chars": 1358,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Badges do\n subject(:client) { Discours"
},
{
"path": "spec/discourse_api/api/categories_spec.rb",
"chars": 6907,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Categories do\n subject(:client) { Disc"
},
{
"path": "spec/discourse_api/api/email_spec.rb",
"chars": 1322,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Email do\n subject(:client) { Discourse"
},
{
"path": "spec/discourse_api/api/groups_spec.rb",
"chars": 5932,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Groups do\n subject(:client) { Discours"
},
{
"path": "spec/discourse_api/api/invite_spec.rb",
"chars": 3574,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Invite do\n subject(:client) { Discours"
},
{
"path": "spec/discourse_api/api/notifications_spec.rb",
"chars": 835,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Notifications do\n subject(:client) { D"
},
{
"path": "spec/discourse_api/api/params_spec.rb",
"chars": 1282,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Params do\n def params_for(h)\n Disco"
},
{
"path": "spec/discourse_api/api/polls_spec.rb",
"chars": 2722,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Polls do\n subject(:client) { Discourse"
},
{
"path": "spec/discourse_api/api/posts_spec.rb",
"chars": 2286,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Posts do\n let(:client) { DiscourseApi:"
},
{
"path": "spec/discourse_api/api/private_messages_spec.rb",
"chars": 2063,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::PrivateMessages do\n subject(:client) {"
},
{
"path": "spec/discourse_api/api/search_spec.rb",
"chars": 1005,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Search do\n subject(:client) { Discours"
},
{
"path": "spec/discourse_api/api/site_settings_spec.rb",
"chars": 522,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::SiteSettings do\n subject(:client) { Di"
},
{
"path": "spec/discourse_api/api/sso_spec.rb",
"chars": 1998,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::SSO do\n subject(:client) { DiscourseAp"
},
{
"path": "spec/discourse_api/api/topics_spec.rb",
"chars": 8126,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Topics do\n subject(:client) { Discours"
},
{
"path": "spec/discourse_api/api/uploads_spec.rb",
"chars": 1105,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Uploads do\n subject(:client) { Discour"
},
{
"path": "spec/discourse_api/api/user_actions_spec.rb",
"chars": 1396,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::UserActions do\n subject(:client) { Dis"
},
{
"path": "spec/discourse_api/api/users_spec.rb",
"chars": 12930,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Users do\n subject(:client) { Discourse"
},
{
"path": "spec/discourse_api/client_spec.rb",
"chars": 5645,
"preview": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::Client do\n subject(:client) { DiscourseApi:"
},
{
"path": "spec/discourse_api/single_sign_on_spec.rb",
"chars": 1868,
"preview": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::SingleSignOn do\n describe DiscourseApi::Si"
},
{
"path": "spec/fixtures/admin_user.json",
"chars": 2871,
"preview": "{\n \"id\": 4,\n \"username\": \"47f6974294f6e3eb9686\",\n \"avatar_template\": \"/letter_avatar_proxy/v2/letter/4/8e8cbc/{size}."
},
{
"path": "spec/fixtures/api_key.json",
"chars": 273,
"preview": "{\n \"key\": {\n \"id\": 5,\n \"key\": \"e722e04df8cf6d097d565ca04eea1ff8e9e8f09beb405bae6f0c79852916f334\",\n \"user\": {\n "
},
{
"path": "spec/fixtures/backups.json",
"chars": 304,
"preview": "[\n {\n \"filename\": \"discourse-2015-01-10-065015.tar.gz\",\n \"size\": 557075,\n \"link\": \"//localhost:3000/admin/back"
},
{
"path": "spec/fixtures/badges.json",
"chars": 18291,
"preview": "{\n \"badges\": [\n {\n \"id\": 9,\n \"name\": \"Autobiographer\",\n \"description\": null,\n \"grant_count\": 0,\n"
},
{
"path": "spec/fixtures/categories.json",
"chars": 1922,
"preview": "{\n \"featured_users\": [],\n \"category_list\": {\n \"can_create_category\": false,\n \"can_create_topic\": false,\n \"dra"
},
{
"path": "spec/fixtures/category_latest_topics.json",
"chars": 2532,
"preview": "{\n \"users\": [\n {\n \"id\": 3,\n \"username\": \"fuzzy\",\n \"avatar_template\": \"//www.gravatar.com/avatar/c0c59"
},
{
"path": "spec/fixtures/category_topics.json",
"chars": 2531,
"preview": "{\n \"users\": [\n {\n \"id\": 3,\n \"username\": \"fuzzy\",\n \"avatar_template\": \"//www.gravatar.com/avatar/c0c59"
},
{
"path": "spec/fixtures/create_topic_with_tags.json",
"chars": 1480,
"preview": "{\n \"id\": 29,\n \"name\": null,\n \"username\": \"blake\",\n \"avatar_template\": \"/user_avatar/localhost/blake/{size}/3_2.png\","
},
{
"path": "spec/fixtures/email_list_all.json",
"chars": 18289,
"preview": "[\n {\n \"id\": 64,\n \"reply_key\": null,\n \"to_address\": \"steve7@example.com\",\n \"email_type\": \"signup\",\n \"user"
},
{
"path": "spec/fixtures/email_settings.json",
"chars": 172,
"preview": "{\n \"delivery_method\": \"smtp\",\n \"settings\": [\n {\n \"name\": \"address\",\n \"value\": \"localhost\"\n },\n {\n "
},
{
"path": "spec/fixtures/group.json",
"chars": 176,
"preview": "{\"basic_group\": {\n \"id\": 101,\n \"name\": \"group_1\",\n \"user_count\": 17,\n \"automatic\": false,\n "
},
{
"path": "spec/fixtures/groups.json",
"chars": 328,
"preview": "[\n {\n \"id\": 101,\n \"name\": \"group_1\",\n \"user_count\": 17,\n \"automatic\": false,\n \"ali"
},
{
"path": "spec/fixtures/hot.json",
"chars": 3104,
"preview": "{\n \"users\": [\n {\n \"id\": 1,\n \"username\": \"test_users\",\n \"avatar_template\": \"//www.gravatar.com/avatar/"
},
{
"path": "spec/fixtures/latest.json",
"chars": 3399,
"preview": "{\n \"users\": [\n {\n \"id\": 1,\n \"username\": \"test_users\",\n \"avatar_template\": \"//www.gravatar.com/avatar/"
},
{
"path": "spec/fixtures/list_api_keys.json",
"chars": 262,
"preview": "{\n \"keys\": [\n {\n \"id\": 1,\n \"key\": \"test_d7fd0429940\",\n \"user\": {\n \"id\": 1,\n \"username\":"
},
{
"path": "spec/fixtures/members_0.json",
"chars": 8392,
"preview": "{\n \"members\": [{\n \"id\": 1,\n \"name\": \"Test 0\",\n \"username\": \"Test_0\"\n }, {\n \"id\": 2,\n "
},
{
"path": "spec/fixtures/members_1.json",
"chars": 7850,
"preview": "{\n \"members\": [{\n \"id\": 101,\n \"name\": \"Test 100\",\n \"username\": \"Test_100\"\n }, {\n \"id\":"
},
{
"path": "spec/fixtures/members_2.json",
"chars": 9086,
"preview": "{\n \"members\": [{\n \"id\": 1,\n \"name\": \"Test 0\",\n \"username\": \"Test_0\"\n }, {\n \"id\": 2,\n "
},
{
"path": "spec/fixtures/new.json",
"chars": 3087,
"preview": "{\n \"users\": [\n {\n \"id\": 1,\n \"username\": \"test_user\",\n \"avatar_template\": \"//www.gravatar.com/avatar/t"
},
{
"path": "spec/fixtures/notification_success.json",
"chars": 22,
"preview": "{\n \"success\": \"OK\"\n}\n"
},
{
"path": "spec/fixtures/notifications.json",
"chars": 376,
"preview": "[\n {\n \"notification_type\": 9,\n \"read\": false,\n \"created_at\": \"2014-04-02T11:58:00.670-04:00\",\n \"post_number"
},
{
"path": "spec/fixtures/polls_toggle_status.json",
"chars": 1452,
"preview": "{\n \"poll\": {\n \"name\": \"poll\",\n \"type\": \"regular\",\n \"status\": \"closed\",\n \"public\": true,\n \"results\": \"alw"
},
{
"path": "spec/fixtures/polls_vote.json",
"chars": 1744,
"preview": "{\n \"poll\": {\n \"name\": \"poll\",\n \"type\": \"regular\",\n \"status\": \"open\",\n \"public\": true,\n \"results\": \"alway"
},
{
"path": "spec/fixtures/polls_voters.json",
"chars": 521,
"preview": "{\n \"voters\": {\n \"e539a9df8700d0d05c69356a07b768cf\": [\n {\n \"id\": 356,\n \"username\": \"David_Ashby\",\n"
},
{
"path": "spec/fixtures/post.json",
"chars": 2867,
"preview": "{\n \"id\": 11,\n \"name\": \"system\",\n \"username\": \"system\",\n \"avatar_template\": \"/user_avatar/localhost/system/{size}/1.p"
},
{
"path": "spec/fixtures/post_action_users.json",
"chars": 426,
"preview": "{\n \"post_action_users\": [\n {\n \"id\": 1286,\n \"username\": \"john_smith\",\n \"avatar_template\": \"/user_avat"
},
{
"path": "spec/fixtures/posts_before.json",
"chars": 5747,
"preview": "{\n \"latest_posts\": [\n {\n \"id\": 14,\n \"name\": null,\n \"username\": \"johndoe\",\n \"avatar_template\": \"/"
},
{
"path": "spec/fixtures/posts_latest.json",
"chars": 7181,
"preview": "{\n \"latest_posts\": [\n {\n \"id\": 15,\n \"name\": null,\n \"username\": \"johndoe\",\n \"avatar_template\": \"/"
},
{
"path": "spec/fixtures/private_messages.json",
"chars": 1810,
"preview": "{\n \"users\": [\n {\n \"id\": 1,\n \"username\": \"test_user\",\n \"uploaded_avatar_id\": null,\n \"avatar_templ"
},
{
"path": "spec/fixtures/regenerate_api_key.json",
"chars": 149,
"preview": "{\n \"api_key\": {\n \"id\": 10,\n \"key\": \"d8a80230fd14531c9b4809c7800b1fe78073a3771d3d2a56a5671bace55bc393\",\n"
},
{
"path": "spec/fixtures/replies.json",
"chars": 1085,
"preview": "{\n \"user_actions\": [\n {\n \"action_type\": 5,\n \"created_at\": \"2016-06-29T20:27:55.767Z\",\n "
},
{
"path": "spec/fixtures/replies_and_topics.json",
"chars": 1743,
"preview": "{\n \"user_actions\": [\n {\n \"action_type\": 5,\n \"created_at\": \"2016-08-10T14:52:49.660Z\",\n \"excerpt\": \"te"
},
{
"path": "spec/fixtures/retrieve_invite.json",
"chars": 3165,
"preview": "{\n \"id\": 26,\n \"invite_key\": \"CGUHjNC4Na\",\n \"link\": \"http://localhost:3000/invites/CGUHjNC4Na\",\n \"email\": \"foo@bar.co"
},
{
"path": "spec/fixtures/search.json",
"chars": 404,
"preview": "[\n {\n \"type\": \"topic\",\n \"name\": \"Topics\",\n \"more\": true,\n \"results\": [\n {\n \"id\": 1,\n \"ti"
},
{
"path": "spec/fixtures/top.json",
"chars": 3234,
"preview": "{\n \"topic_list\": {\n \"can_create_topic\": false,\n \"more_topics_url\": \"/latest.json?page=1\",\n \"draft\": null,\n "
},
{
"path": "spec/fixtures/topic.json",
"chars": 21729,
"preview": "{\n \"post_stream\": {\n \"posts\": [\n {\n \"id\": 83,\n \"name\": \"Sam Saffron\",\n \"username\": \"samsaf"
},
{
"path": "spec/fixtures/topic_invite_user.json",
"chars": 22,
"preview": "{\n \"success\": true\n}\n"
},
{
"path": "spec/fixtures/topic_posts.json",
"chars": 1872,
"preview": "{\n \"post_stream\":{\n \"posts\":[\n {\n \"id\":123456,\n \"name\":\"Someones Name\",\n \"username\":\"Some-"
},
{
"path": "spec/fixtures/topics_created_by.json",
"chars": 1240,
"preview": "{\n \"users\": [\n {\n \"id\": 1,\n \"username\": \"test_user\",\n \"avatar_template\": \"//www.gravatar.com/avatar/t"
},
{
"path": "spec/fixtures/update_trust_level.json",
"chars": 1009,
"preview": "{\n \"admin_user\": {\n \"id\": 2,\n \"username\": \"robin\",\n \"uploaded_avatar_id\": 3,\n \"avatar_template\": \"/user_ava"
},
{
"path": "spec/fixtures/upload_avatar.json",
"chars": 391,
"preview": "{\n \"id\": 11,\n \"user_id\": 1,\n \"original_filename\": \"avatar.jpg\",\n \"filesize\": 86900,\n \"width\": 360,\n \"height\": 360,"
},
{
"path": "spec/fixtures/upload_file.json",
"chars": 396,
"preview": "{\n \"id\": 11,\n \"user_id\": 1,\n \"original_filename\": \"grumpy_cat.gif\",\n \"filesize\": 86900,\n \"width\": 360,\n \"height\": "
},
{
"path": "spec/fixtures/user.json",
"chars": 1830,
"preview": "{\n \"user_badges\": [],\n \"user\": {\n \"id\": 1,\n \"username\": \"test_user\",\n \"uploaded_avatar_id\": null,\n \"avatar"
},
{
"path": "spec/fixtures/user_activate_success.json",
"chars": 22,
"preview": "{\n \"success\": \"OK\"\n}\n"
},
{
"path": "spec/fixtures/user_badges.json",
"chars": 4019,
"preview": "{\n \"user_badges\": [\n {\n \"id\": 3,\n \"granted_at\": \"2014-11-30T14:27:19.404Z\",\n \"badge_id\": 10,\n \"u"
},
{
"path": "spec/fixtures/user_create_success.json",
"chars": 204,
"preview": "{\n \"success\": true,\n \"active\": false,\n \"message\": \"You're almost done! We sent an activation email to <b>test2@exampl"
},
{
"path": "spec/fixtures/user_grant_admin.json",
"chars": 1116,
"preview": "{\n \"admin_user\": {\n \"id\": 11,\n \"username\": \"dave4\",\n \"uploaded_avatar_id\": null,\n \"avatar"
},
{
"path": "spec/fixtures/user_grant_moderator.json",
"chars": 1116,
"preview": "{\n \"admin_user\": {\n \"id\": 11,\n \"username\": \"dave4\",\n \"uploaded_avatar_id\": null,\n \"avatar"
},
{
"path": "spec/fixtures/user_list.json",
"chars": 15361,
"preview": "[\n {\n \"id\": 1,\n \"username\": \"test_user\",\n \"uploaded_avatar_id\": 7,\n \"avatar_template\": \"/user_avatar/localh"
},
{
"path": "spec/fixtures/user_log_out_success.json",
"chars": 22,
"preview": "{\n \"success\": \"OK\"\n}\n"
},
{
"path": "spec/fixtures/user_update_avatar_success.json",
"chars": 22,
"preview": "{\n \"success\": \"OK\"\n}\n"
},
{
"path": "spec/fixtures/user_update_user.json",
"chars": 22,
"preview": "{\n \"success\": true\n}\n"
},
{
"path": "spec/fixtures/user_update_username.json",
"chars": 43,
"preview": "{\n \"id\": 7,\n \"username\": \"fake_user_2\"\n}\n"
},
{
"path": "spec/spec_helper.rb",
"chars": 1102,
"preview": "# frozen_string_literal: true\nrequire \"simplecov\"\n\nSimpleCov.formatter =\n SimpleCov::Formatter::MultiFormatter.new([Sim"
}
]
About this extraction
This page contains the full source code of the discourse/discourse_api GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 139 files (306.0 KB), approximately 93.2k tokens, and a symbol index with 249 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.