linoleum-club/db.rb

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