112 lines
3.6 KiB
Ruby
112 lines
3.6 KiB
Ruby
require "sequel"
|
|
|
|
# :created_at and :updated_at on all tables
|
|
Sequel::Model.plugin :timestamps, update_on_create: true
|
|
|
|
DB = Sequel.connect("sqlite://db.sqlite3")
|
|
|
|
|
|
# Puzzle model
|
|
# Sequel creates getters and setters corresponding to DB columns. DB is the source of truth.
|
|
# Sequel tracks which migrations have been run, and you can use it to run migrations
|
|
# We need to write migrations manually
|
|
class Puzzle < Sequel::Model
|
|
# Fields:
|
|
# primary_key :id
|
|
|
|
# # The markdown text of the message
|
|
# String :text, null: false
|
|
|
|
# # One of these must be present
|
|
# String :solution, null: true # A string to compare against
|
|
# String :check_solution, null: true # a ruby expression that will evaluate to true or false. `discord_username` and `submission` are in scope.
|
|
|
|
# # We need a public_id or title that we can use to identify the puzzle, for example when upserting toml
|
|
# # This is publicly displayed in hex, but stored as an integer so that we can sort
|
|
# # This is the order that the puzzles are released
|
|
# # Unreleased puzzles may have a null public_id
|
|
# Integer :public_id, null: true
|
|
|
|
# # To facilitate editing puzzle messages, we want to store the Discord message id
|
|
# # Obviously null if it hasn't been sent yet
|
|
# Integer :message_id, null: true
|
|
|
|
# # draft / ready / current / used
|
|
# String :state, default: "draft", null: false
|
|
|
|
# Author ID, the Discord ID of the author/submitter of the puzzle
|
|
# String :author_id, null: false
|
|
|
|
# # Timestamps
|
|
# DateTime :created_at, null: false
|
|
# DateTime :updated_at, null: false
|
|
|
|
|
|
# Scopes
|
|
class << self
|
|
def draft
|
|
where state: "draft"
|
|
end
|
|
def ready
|
|
where state: "ready"
|
|
end
|
|
def current
|
|
where state: "current"
|
|
end
|
|
def used
|
|
where state: "used"
|
|
end
|
|
end
|
|
|
|
# Validation
|
|
plugin :validation_helpers
|
|
def validate
|
|
errors.add(:solution, "must be present") if solution.blank? and check_solution.blank?
|
|
validates_presence :text
|
|
end
|
|
|
|
# Getters (not in a literal Java sense, but functions that don't mutate and return some info)
|
|
|
|
# The text that will be sent for this puzzle's message
|
|
def get_message_text
|
|
# Author attribution line
|
|
# Puzzle_send_job relies on the second thing returned here being the main puzzle text
|
|
author_attr = author_id != $me.id ? "*This guest puzzle was written by <@#{author_id}>*\n" : ""
|
|
return [
|
|
"**Puzzle 0x#{display_id}**\nAccess to the club has been reset. To re-enter, please solve the new puzzle. <@&#{$puzzle_pings_role.id}>\n#{author_attr}",
|
|
text,
|
|
"Submit your answer with `/solve` in <#1140124119174754429>. If you have an idea for a future puzzle, use `/add-puzzle` for information about how to create one."
|
|
]
|
|
end
|
|
|
|
# Gets this puzzle's Discord message ID (cached)
|
|
def message
|
|
@message ||= $puzzle_channel.load_message message_id
|
|
end
|
|
|
|
def display_id
|
|
return public_id.to_s(16)
|
|
end
|
|
end
|
|
|
|
|
|
class Solve < Sequel::Model
|
|
# One puzzle has many solves, but one solve has one puzzle
|
|
many_to_one :puzzle
|
|
|
|
# Fields:
|
|
# primary_key :id
|
|
|
|
# # The user's Discord id
|
|
# String :discord_id, null: false
|
|
|
|
# # The ID of the puzzle solved
|
|
# foreign_key :puzzle_id, :puzzles, null: false
|
|
|
|
# # Timestamps
|
|
# DateTime :created_at, null: false
|
|
# DateTime :updated_at, null: false
|
|
|
|
|
|
end
|