diff --git a/db/migrations/20200830172615_initial.sql b/db/migrations/20200830172615_initial.sql
index 9e5234a..e3bad95 100644
--- a/db/migrations/20200830172615_initial.sql
+++ b/db/migrations/20200830172615_initial.sql
@@ -101,6 +101,8 @@ WITH stackcoin_reserve_system_user AS (
)
INSERT INTO "internal_user" SELECT id, username AS identifier FROM stackcoin_reserve_system_user;
+SELECT setval('"user_id_seq"', (SELECT MAX(id) FROM "user"));
+
COMMIT;
-- +micrate Down
diff --git a/docker-compose.yml b/docker-compose.yml
index 7dca882..6b5aebb 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,3 @@
-version: "3"
-
services:
stackcoin:
build: ./.
@@ -8,31 +6,24 @@ services:
- ./.env
ports:
- 127.0.0.1:3000:3000
- networks:
- - backend
- postgres:
- image: postgres:12
+ db:
+ image: postgres:17
env_file:
- ./.env
ports:
- 127.0.0.1:5432:5432
- networks:
- - backend
volumes:
- db:/var/lib/postgresql/data
hasura:
- image: hasura/graphql-engine:v2.0.3
+ image: hasura/graphql-engine:v2
env_file:
- ./.env
ports:
- 127.0.0.1:8080:8080
depends_on:
- - postgres
+ - db
volumes:
db:
-
-networks:
- backend:
diff --git a/shard.lock b/shard.lock
index 9702966..64ea12e 100644
--- a/shard.lock
+++ b/shard.lock
@@ -2,15 +2,15 @@ version: 2.0
shards:
backtracer:
git: https://github.com/sija/backtracer.cr.git
- version: 1.2.1
+ version: 1.2.2
db:
git: https://github.com/crystal-lang/crystal-db.git
- version: 0.10.1
+ version: 0.11.0
discordcr:
- git: https://github.com/shardlab/discordcr.git
- version: 0.4.1
+ git: https://github.com/soya-daizu/discordcr.git
+ version: 0.4.0+git.commit.a409ca150bc9a8fcd42297520a59ddd67ffaebce
dotenv:
git: https://github.com/gdotdesign/cr-dotenv.git
@@ -18,41 +18,57 @@ shards:
exception_page:
git: https://github.com/crystal-loot/exception_page.git
- version: 0.2.0
+ version: 0.5.0
+
+ graphql:
+ git: https://github.com/graphql-crystal/graphql.git
+ version: 0.4.0
humanize_time:
git: https://github.com/mamantoha/humanize_time.git
- version: 0.10.1
+ version: 0.12.2
i18n:
git: https://github.com/crystal-i18n/i18n.git
- version: 0.2.0
+ version: 0.2.1
+
+ jbuilder:
+ git: https://github.com/shootingfly/jbuilder.git
+ version: 1.0.0
kemal:
git: https://github.com/kemalcr/kemal.git
- version: 1.0.0+git.commit.218be2422172d330feb62c6a8abc7df5402fdb84
+ version: 1.6.0+git.commit.749c537e853af6f032b01cf2b91ae2e740340d62
- kilt:
- git: https://github.com/jeromegn/kilt.git
- version: 0.6.1
+ logger:
+ git: https://github.com/crystal-lang/logger.cr.git
+ version: 0.1.0
micrate:
git: https://github.com/amberframework/micrate.git
- version: 0.11.0
+ version: 0.15.1
mysql:
git: https://github.com/crystal-lang/crystal-mysql.git
- version: 0.13.0
+ version: 0.14.0
pg:
git: https://github.com/will/crystal-pg.git
- version: 0.23.2
+ version: 0.26.0
radix:
git: https://github.com/luislavena/radix.git
version: 0.4.1
+ runcobo:
+ git: https://github.com/runcobo/runcobo.git
+ version: 2.0.0
+
sqlite3:
git: https://github.com/crystal-lang/crystal-sqlite3.git
- version: 0.18.0
+ version: 0.19.0
+
+ water:
+ git: https://github.com/shootingfly/water.git
+ version: 1.0.0
diff --git a/shard.yml b/shard.yml
index c35e038..c74be75 100644
--- a/shard.yml
+++ b/shard.yml
@@ -15,11 +15,14 @@ dependencies:
pg:
github: will/crystal-pg
+ logger:
+ github: crystal-lang/logger.cr
+
micrate:
github: amberframework/micrate
discordcr:
- github: shardlab/discordcr
+ github: soya-daizu/discordcr
dotenv:
github: gdotdesign/cr-dotenv
@@ -31,13 +34,11 @@ dependencies:
github: kemalcr/kemal
branch: master
- #runcobo:
- # github: runcobo/runcobo
-
sqlite3:
github: crystal-lang/crystal-sqlite3
- #graphql:
- # github: graphql-crystal/graphql
+ runcobo:
+ github: runcobo/runcobo
-crystal: 1.0.0
+ graphql:
+ github: graphql-crystal/graphql
diff --git a/spec/stubs.cr b/spec/stubs.cr
index bc93a01..6a04d3d 100644
--- a/spec/stubs.cr
+++ b/spec/stubs.cr
@@ -5,19 +5,19 @@ record MessageAuthor, id : Discord::Snowflake, username : String, avatar_url : S
end
end
-record MessageStub, channel_id : Discord::Snowflake, guild_id : Discord::Snowflake, content : String, author : MessageAuthor do
+record MessageStub, channel_id : Discord::Snowflake, guild_id : Discord::Snowflake, content : String, author : MessageAuthor, message_reference : Discord::MessageReference? do
def self.new(channel_id, guild_id, content, author)
channel_id = Discord::Snowflake.new(channel_id.to_u64)
guild_id = Discord::Snowflake.new(guild_id.to_u64)
- new(channel_id, guild_id, content, author)
+ new(channel_id, guild_id, content, author, nil)
end
end
-record MessageWithEmbedStub, channel_id : Discord::Snowflake, guild_id : Discord::Snowflake, content : String, author : MessageAuthor, embed : Discord::Embed do
+record MessageWithEmbedStub, channel_id : Discord::Snowflake, guild_id : Discord::Snowflake, content : String, author : MessageAuthor, embed : Discord::Embed, message_reference : Discord::MessageReference? do
def self.new(channel_id, guild_id, content, author, embed)
channel_id = Discord::Snowflake.new(channel_id.to_u64)
guild_id = Discord::Snowflake.new(guild_id.to_u64)
- new(channel_id, guild_id, content, author, embed)
+ new(channel_id, guild_id, content, author, embed, nil)
end
end
@@ -33,12 +33,12 @@ class MockClient
# TODO class_property current_guild = CSBOIS_GUILD_SNOWFLAKE
- def create_message(channel_id : Discord::Snowflake, content : String)
- # TODO MessageStub.new(channel_id, @@current_guild, content)
+ def create_message(channel_id : Discord::Snowflake, content : String, message_reference : Discord::MessageReference? = nil)
+ # TODO MessageStub.new(channel_id, @@current_guild, content, message_reference)
end
- def create_message(channel_id : Discord::Snowflake, content : String, embed : Discord::Embed)
- # TODO MessageWithEmbedStub.new(channel_id, @@current_guild, content, embed)
+ def create_message(channel_id : Discord::Snowflake, content : String, embed : Discord::Embed, message_reference : Discord::MessageReference? = nil)
+ # TODO MessageWithEmbedStub.new(channel_id, @@current_guild, content, embed, message_reference)
end
def create_dm(user_id : Discord::Snowflake)
diff --git a/src/cli.cr b/src/cli.cr
index 6f6823f..70e0388 100644
--- a/src/cli.cr
+++ b/src/cli.cr
@@ -38,11 +38,10 @@ parser = OptionParser.parse do |parser|
exit
end
- # TODO bringb back api
- # parser.on("-s", "--schema", "Print the schema of the internal GraphQL Api") do
- # puts StackCoin::Api::Internal::Gql.schema.document.to_s
- # exit
- # end
+ parser.on("-s", "--schema", "Print the schema of the internal GraphQL Api") do
+ puts StackCoin::Api::Internal::Gql.schema.document.to_s
+ exit
+ end
parser.on("-h", "--help", "Show this help") do
puts parser
diff --git a/src/stackcoin.cr b/src/stackcoin.cr
index b3f4544..895c416 100644
--- a/src/stackcoin.cr
+++ b/src/stackcoin.cr
@@ -2,19 +2,18 @@ require "./stackcoin/config"
require "./stackcoin/db"
require "./stackcoin/core"
require "./stackcoin/bot"
-
-# TODO bring back api
-# require "./stackcoin/api"
+require "./stackcoin/api"
module StackCoin
+ TMP_DIR = "/tmp/stackcoin/"
+
def self.run!
- Dir.mkdir_p("/tmp/stackcoin/")
+ Dir.mkdir_p(TMP_DIR)
run_migrations
- # TODO bring back api
- # spawn(Api::External.run!)
- # spawn(Api::Internal.run!)
+ spawn(Api::External.run!)
+ spawn(Api::Internal.run!)
spawn(Bot.run!)
diff --git a/src/stackcoin/api/external.cr b/src/stackcoin/api/external.cr
index d57c8e6..e038060 100644
--- a/src/stackcoin/api/external.cr
+++ b/src/stackcoin/api/external.cr
@@ -18,7 +18,11 @@ class StackCoin::Api::External::Auth < BaseAction
cookie = Core::SessionStore::Session.to_cookie(result.new_session_id)
context.response.cookies << cookie
- render_plain("TODO redirect") # TODO redirect
+ context = render_plain("redirecting")
+
+ context.response.status_code = 303
+ context.response.headers["Location"] = "/"
+ context
else
render_plain(result.message)
end
@@ -33,6 +37,55 @@ class StackCoin::Api::External::Default < BaseAction
get "/*"
call do |context|
- render_plain("...")
+ context = render_plain(<<-HTML
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stackcoin
+ this website has no functionality, at the moment
+
+
+
+ HTML
+ )
+
+ context.response.content_type = "text/html"
+ context
end
end
diff --git a/src/stackcoin/api/internal.cr b/src/stackcoin/api/internal.cr
index 0e50654..ef91d89 100644
--- a/src/stackcoin/api/internal.cr
+++ b/src/stackcoin/api/internal.cr
@@ -1,4 +1,5 @@
require "http/server"
+require "log"
class StackCoin::Api::Internal
end
@@ -6,10 +7,20 @@ end
require "./internal/*"
class StackCoin::Api::Internal
- def self.not_found(r)
- r.status_code = 404
+ Log = ::Log.for("stackcoin.api.internal")
+
+ def self.basic_message(r, message)
+ r.status_code = 200
r.content_type = "text/plain"
- r.print("Not found")
+ r.print(message)
+ end
+
+ def self.not_found(r)
+ basic_message(r, "Not found")
+ end
+
+ def self.invalid_method(r)
+ basic_message(r, "Invalid method")
end
class SchemaExecuteInput
@@ -32,7 +43,7 @@ class StackCoin::Api::Internal
case resource
when "/auth"
unless method == "GET"
- next not_found(r)
+ next invalid_method(r)
end
if token = context.request.headers["Authorization"]?
@@ -59,7 +70,7 @@ class StackCoin::Api::Internal
next
when "/graphql"
unless method == "POST"
- next not_found(r)
+ next invalid_method(r)
end
headers = context.request.headers
@@ -92,6 +103,7 @@ class StackCoin::Api::Internal
end
address = server.bind_tcp(4000)
+ Log.info { "Listening on #{address}" }
server.listen
end
end
diff --git a/src/stackcoin/api/internal/graphql.cr b/src/stackcoin/api/internal/graphql.cr
index dba49b8..79b1a32 100644
--- a/src/stackcoin/api/internal/graphql.cr
+++ b/src/stackcoin/api/internal/graphql.cr
@@ -61,8 +61,8 @@ class StackCoin::Api::Internal::Gql
include GraphQL::QueryType
@[GraphQL::Field]
- def pid : Int64
- Process.pid
+ def pid : String
+ Process.pid.to_s
end
end
diff --git a/src/stackcoin/bot.cr b/src/stackcoin/bot.cr
index d128c7f..ba08124 100644
--- a/src/stackcoin/bot.cr
+++ b/src/stackcoin/bot.cr
@@ -37,8 +37,7 @@ class StackCoin::Bot
Commands::Dole.new,
Commands::Graph.new,
Commands::Leaderboard.new,
- # TODO bring back when api is ready
- # Commands::Login.new,
+ Commands::Login.new,
Commands::Mark.new,
Commands::Open.new,
Commands::Profile.new,
@@ -85,7 +84,11 @@ class StackCoin::Bot
end
def send_message(message, content)
- @client.create_message(message.channel_id, content)
+ @client.create_message(
+ message.channel_id,
+ content,
+ message_reference: message.message_reference,
+ )
end
def handle_message(message)
@@ -94,7 +97,7 @@ class StackCoin::Bot
return if parsed.nil?
valid_check = Core::Group.validate_group_channel(message.guild_id, message.channel_id)
- unless valid_check.is_a?(Core::Group::Result::ValidGroupChannel)
+ unless valid_check.is_a?(Core::Group::Result::ValidChannel) || parsed.command == "mark"
send_message(message, valid_check.message)
return
end
diff --git a/src/stackcoin/bot/command.cr b/src/stackcoin/bot/command.cr
index ce992b3..640afdc 100644
--- a/src/stackcoin/bot/command.cr
+++ b/src/stackcoin/bot/command.cr
@@ -25,7 +25,11 @@ class StackCoin::Bot
end
def send_message(message, content)
- client.create_message(message.channel_id, content)
+ client.create_message(
+ message.channel_id,
+ content,
+ message_reference: message.message_reference,
+ )
end
def send_embed(message, emb : Discord::Embed)
@@ -39,7 +43,12 @@ class StackCoin::Bot
text: "StackCoin™",
icon_url: "https://i.imgur.com/CsVxtvM.png"
)
- client.create_message(message.channel_id, content, emb)
+ client.create_message(
+ message.channel_id,
+ content,
+ emb,
+ message_reference: message.message_reference,
+ )
end
end
end
diff --git a/src/stackcoin/bot/commands/login.cr b/src/stackcoin/bot/commands/login.cr
index f962a9b..75b4e61 100644
--- a/src/stackcoin/bot/commands/login.cr
+++ b/src/stackcoin/bot/commands/login.cr
@@ -27,7 +27,10 @@ class StackCoin::Bot::Commands
Here's your one time login link:
#{result.link}
MESSAGE
- send_message(message, "One time login link sent to you, check your direct messages with this bot")
+
+ unless message.guild_id.nil?
+ send_message(message, "One time login link sent to you, check your direct messages with this bot")
+ end
rescue Discord::CodeException
send_message(message, "Failed to send your a direct message, cannot send one time link via Discord")
end
diff --git a/src/stackcoin/bot/commands/mark.cr b/src/stackcoin/bot/commands/mark.cr
index b4de109..912a12a 100644
--- a/src/stackcoin/bot/commands/mark.cr
+++ b/src/stackcoin/bot/commands/mark.cr
@@ -12,7 +12,23 @@ class StackCoin::Bot::Commands
raise Parser::Error.new("Expected no arguments, got #{parsed.arguments.size}")
end
- send_message(message, "TODO")
+ guild_id = message.guild_id
+
+ unless guild_id.is_a?(Discord::Snowflake)
+ raise Parser::Error.new("Cannot invoke command in DMs")
+ end
+
+ result = nil
+ DB.transaction do |tx|
+ cnn = tx.connection
+ invokee_id = user_id_from_snowflake(cnn, message.author.id)
+ result = Core::Group.set_group_channel(tx, invokee_id, guild_id, message.channel_id)
+ end
+ result = result.as(Result::Base)
+
+ send_message(message, result.message)
+
+ result
end
end
end
diff --git a/src/stackcoin/core/banned.cr b/src/stackcoin/core/banned.cr
index ac3342b..cd26879 100644
--- a/src/stackcoin/core/banned.cr
+++ b/src/stackcoin/core/banned.cr
@@ -21,7 +21,7 @@ class StackCoin::Core::Banned
def self.ban(tx : ::DB::Transaction, invokee_id : Int32?, user_id : Int32?) : Result::Base
unless invokee_id.is_a?(Int32)
- return Result::NoSuchUserAccount.new("You doesn't have a user account")
+ return Result::NoSuchUserAccount.new("You don't have a user account")
end
unless user_id.is_a?(Int32)
@@ -55,7 +55,7 @@ class StackCoin::Core::Banned
def self.unban(tx : ::DB::Transaction, invokee_id : Int32?, user_id : Int32?) : Result::Base
unless invokee_id.is_a?(Int32)
- return Result::NoSuchUserAccount.new("You doesn't have a user account")
+ return Result::NoSuchUserAccount.new("You don't have a user account")
end
unless user_id.is_a?(Int32)
diff --git a/src/stackcoin/core/graph.cr b/src/stackcoin/core/graph.cr
index ae33d01..dadf770 100644
--- a/src/stackcoin/core/graph.cr
+++ b/src/stackcoin/core/graph.cr
@@ -54,7 +54,7 @@ class StackCoin::Core::Graph
end
random = UUID.random
- image_filename = "/tmp/stackcoin/graph_#{user_id}_#{random}.png"
+ image_filename = "#{StackCoin::TMP_DIR}/graph_#{user_id}_#{random}.png"
title = "User ##{user_id} - #{Time.utc}"
process = Process.new(
diff --git a/src/stackcoin/core/group.cr b/src/stackcoin/core/group.cr
index 90ea773..193b196 100644
--- a/src/stackcoin/core/group.cr
+++ b/src/stackcoin/core/group.cr
@@ -15,7 +15,13 @@ class StackCoin::Core::Group
class NotAuthorized < Failure
end
- class ValidGroupChannel < Success
+ class ValidChannel < Success
+ end
+
+ class ValidGroupChannel < ValidChannel
+ end
+
+ class ValidDirectMessage < ValidChannel
end
end
@@ -31,7 +37,7 @@ class StackCoin::Core::Group
def self.validate_group_channel(guild_id : Discord::Snowflake?, channel_id : Discord::Snowflake) : Result::Base
unless guild_id.is_a?(Discord::Snowflake)
- return Result::NoDirectMessage.new("Can't access via direct message")
+ return Result::ValidDirectMessage.new("Valid direct message")
end
designated_channel = if discord_guild_to_channel_cache.includes?(guild_id)
@@ -51,9 +57,14 @@ class StackCoin::Core::Group
Result::ValidGroupChannel.new("Valid group channel")
end
- def self.set_group_channel(tx : ::DB::Transaction, invokee_id : Int32?, guild_id : Discord::Snowflake, channel_id : Discord::Snowflake?)
+ def self.set_group_channel(
+ tx : ::DB::Transaction,
+ invokee_id : Int32?,
+ guild_id : Discord::Snowflake,
+ channel_id : Discord::Snowflake
+ )
unless invokee_id.is_a?(Int32)
- return Result::NoSuchUserAccount.new("You doesn't have a user account")
+ return Result::NoSuchUserAccount.new("You don't have a user account")
end
cnn = tx.connection
@@ -62,8 +73,29 @@ class StackCoin::Core::Group
SELECT admin FROM "user" WHERE id = $1
SQL
- unless chanel_id
- # TODO remove
+ unless invokee_is_admin
+ return Result::NotAuthorized.new("Not authorized to set the group channel")
end
+
+ discord_guild_exists = cnn.query_one(<<-SQL, guild_id, as: Bool)
+ SELECT EXISTS(SELECT 1 FROM "discord_guild" WHERE snowflake = $1)
+ SQL
+
+ unless discord_guild_exists
+ guild = Bot::INSTANCE.cache.resolve_guild(guild_id)
+ cnn.exec(<<-SQL, guild_id, guild.name, guild.icon_url, channel_id, Time.utc)
+ INSERT INTO "discord_guild" (
+ snowflake, name, icon_url, designated_channel_snowflake, last_updated
+ ) VALUES (
+ $1, $2, $3, $4, $5
+ )
+ SQL
+ end
+
+ cnn.exec(<<-SQL, channel_id, guild_id)
+ UPDATE "discord_guild" SET designated_channel_snowflake = $1 WHERE snowflake = $2
+ SQL
+
+ Result::Success.new("Channel set to <##{channel_id}>")
end
end
diff --git a/src/stackcoin/core/session_store.cr b/src/stackcoin/core/session_store.cr
index 7403404..58f1c55 100644
--- a/src/stackcoin/core/session_store.cr
+++ b/src/stackcoin/core/session_store.cr
@@ -33,7 +33,8 @@ class StackCoin::Core::SessionStore
end
def self.one_time_link(id : String)
- URI.encode("#{STACKCOIN_SITE_BASE}/auth?one_time_key=#{id}")
+ encoded_id = URI.encode_path(id)
+ "#{STACKCOIN_SITE_BASE}/auth?one_time_key=#{id}"
end
def self.to_cookie(id : String)
@@ -79,11 +80,11 @@ class StackCoin::Core::SessionStore
end
private def self.is_session_still_valid(session : Session) : Bool
- session.expires_at < Time.utc
+ session.expires_at > Time.utc
end
private def self.is_session_still_valid(session_key : String) : Bool
- if session = in_memory_session_store[one_time_key]?
+ if session = in_memory_session_store[session_key]?
is_session_still_valid(session)
else
false
diff --git a/src/stackcoin/core/stackcoin_reserve_system.cr b/src/stackcoin/core/stackcoin_reserve_system.cr
index 235ea14..2fecaed 100644
--- a/src/stackcoin/core/stackcoin_reserve_system.cr
+++ b/src/stackcoin/core/stackcoin_reserve_system.cr
@@ -96,12 +96,14 @@ class StackCoin::Core::StackCoinReserveSystem
time,
label
) VALUES (
- $1, $2, $3, $4, $5, $5
+ $1, $2, $3, $4, $5, $6
) RETURNING id
SQL
+ new_balance = Bank.balance(cnn, user_id).as(Bank::Result::Balance).balance
+
return Result::Pump.new(
- "Successfully pumped the StackCoin Reserve System with #{amount} STK, with label: \"#{label}\"",
+ "Successfully pumped the StackCoin Reserve System with #{amount} STK, with label: \"#{label}\", the new amount of STK in the reserve is #{new_balance} STK",
pump_id: pump_id,
stackcoin_reserve_system_user_balance: new_balance,
)
diff --git a/the-migration/the-migration.cr b/the-migration/the-migration.cr
index 262d574..2edb877 100644
--- a/the-migration/the-migration.cr
+++ b/the-migration/the-migration.cr
@@ -552,6 +552,35 @@ new_db.transaction do |tx|
new_transactions.each do |new_transaction|
new_transaction.insert(cnn)
end
+
+ cnn.exec(<<-SQL
+ DO $$
+ DECLARE
+ seq RECORD;
+ tables_to_fix text[] := ARRAY['user', 'discord_guild', 'transaction', 'pump', 'request'];
+ BEGIN
+ FOR seq IN
+ SELECT
+ table_name,
+ column_name,
+ pg_get_serial_sequence(format('%I.%I', table_schema, table_name), column_name) AS seq_name
+ FROM
+ information_schema.columns
+ WHERE
+ column_default LIKE 'nextval(%'
+ AND table_name = ANY(tables_to_fix)
+ LOOP
+ EXECUTE format(
+ 'SELECT setval(%L, COALESCE(MAX(%I), 0) + 1, false) FROM %I.%I',
+ seq.seq_name,
+ seq.column_name,
+ 'public',
+ seq.table_name
+ );
+ END LOOP;
+ END $$;
+ SQL
+ )
end
puts "inserted things into the database"