feat: create links - route + controller + model + migration
This commit is contained in:
@@ -4,3 +4,4 @@
|
|||||||
/.shards/
|
/.shards/
|
||||||
*.dwarf
|
*.dwarf
|
||||||
*.db
|
*.db
|
||||||
|
.env.*
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-- +micrate Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
CREATE TABLE links (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
slug VARCHAR(100) UNIQUE NOT NULL,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
click_counter INTEGER DEFAULT 0,
|
||||||
|
created_at INTEGER DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +micrate Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
DROP TABLE links;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: pa
|
name: url-shortener
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
@@ -6,7 +6,7 @@ authors:
|
|||||||
|
|
||||||
targets:
|
targets:
|
||||||
pa:
|
pa:
|
||||||
main: src/pa.cr
|
main: src/url-shortener.cr
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
kemal:
|
kemal:
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
require "../lib/controller.cr"
|
||||||
|
|
||||||
|
module App::Controllers::Link
|
||||||
|
class Create < App::Lib::BaseController
|
||||||
|
include App::Models
|
||||||
|
include App::Lib
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
params = env.params.json["link"].as(Hash)
|
||||||
|
|
||||||
|
link = Link.new
|
||||||
|
link.url = params["url"].as_s
|
||||||
|
link.slug = Random::Secure.urlsafe_base64(4)
|
||||||
|
|
||||||
|
changeset = Database.insert(link)
|
||||||
|
|
||||||
|
if !changeset.valid?
|
||||||
|
errors = {"errors" => map_changeset_errors(changeset.errors)}
|
||||||
|
raise App::UnprocessableEntityException.new(env, errors.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
App::Serializers::Link.new(link).to_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
require "../lib/controller.cr"
|
require "../lib/controller.cr"
|
||||||
|
|
||||||
module Pa::Controllers::Ping
|
module App::Controllers::Ping
|
||||||
class Get < Pa::Lib::BaseController
|
class Get < App::Lib::BaseController
|
||||||
def call(env)
|
def call(env)
|
||||||
response = {"pong" => "ok"}
|
response = {"pong" => "ok"}
|
||||||
response.to_json
|
response.to_json
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
module Pa::Lib
|
module App::Lib
|
||||||
abstract class BaseController
|
abstract class BaseController
|
||||||
|
def map_changeset_errors(errors)
|
||||||
|
errors.reduce({} of String => Array(String)) do |memo, error|
|
||||||
|
memo[error[:field]] = memo[error[:field]]? || [] of String
|
||||||
|
memo[error[:field]] << error[:message]
|
||||||
|
memo
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+2
-3
@@ -1,7 +1,7 @@
|
|||||||
require "sqlite3"
|
require "sqlite3"
|
||||||
require "crecto"
|
require "crecto"
|
||||||
|
|
||||||
module Pa::Lib
|
module App::Lib
|
||||||
class Database
|
class Database
|
||||||
extend Crecto::Repo
|
extend Crecto::Repo
|
||||||
|
|
||||||
@@ -9,8 +9,7 @@ module Pa::Lib
|
|||||||
Multi = Crecto::Repo::Multi
|
Multi = Crecto::Repo::Multi
|
||||||
|
|
||||||
config do |conf|
|
config do |conf|
|
||||||
conf.adapter = Crecto::Adapters::SQLite3
|
conf.uri = ENV["DATABASE_URL"]
|
||||||
conf.database = ENV["DATABASE_URL"]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
require "kemal"
|
||||||
|
|
||||||
|
module App
|
||||||
|
class UnauthorizedException < Kemal::Exceptions::CustomException
|
||||||
|
def initialize(context)
|
||||||
|
context.response.status_code = 401
|
||||||
|
super(context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ForbiddenException < Kemal::Exceptions::CustomException
|
||||||
|
def initialize(context)
|
||||||
|
context.response.status_code = 403
|
||||||
|
super(context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class NotFoundException < Kemal::Exceptions::CustomException
|
||||||
|
def initialize(context)
|
||||||
|
context.response.status_code = 404
|
||||||
|
super(context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class UnprocessableEntityException < Kemal::Exceptions::CustomException
|
||||||
|
def initialize(context, @content = "")
|
||||||
|
context.response.status_code = 422
|
||||||
|
super(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
getter :content
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
require "sqlite3"
|
||||||
|
require "crecto"
|
||||||
|
|
||||||
|
module App::Models
|
||||||
|
class Link < Crecto::Model
|
||||||
|
schema :links do
|
||||||
|
field :slug, String
|
||||||
|
field :url, String
|
||||||
|
field :click_counter, Int64
|
||||||
|
end
|
||||||
|
|
||||||
|
validate_required [:slug, :url]
|
||||||
|
end
|
||||||
|
end
|
||||||
+9
-1
@@ -1,7 +1,15 @@
|
|||||||
require "./controllers/**"
|
require "./controllers/**"
|
||||||
|
|
||||||
module Pa
|
module App
|
||||||
|
before_all do |env|
|
||||||
|
env.response.content_type = "application/json"
|
||||||
|
end
|
||||||
|
|
||||||
get "/api/ping" do |env|
|
get "/api/ping" do |env|
|
||||||
Controllers::Ping::Get.new.call(env)
|
Controllers::Ping::Get.new.call(env)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post "/api/links" do |env|
|
||||||
|
Controllers::Link::Create.new.call(env)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
require "json"
|
||||||
|
|
||||||
|
require "../models/link"
|
||||||
|
|
||||||
|
module App::Serializers
|
||||||
|
class Link
|
||||||
|
def initialize(@link : App::Models::Link)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_json(builder : JSON::Builder)
|
||||||
|
builder.object do
|
||||||
|
builder.field("origin", @link.url)
|
||||||
|
builder.field("link", "#{ENV["APP_URL"]}/#{@link.slug}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
require "kemal"
|
require "kemal"
|
||||||
|
|
||||||
require "./config/*"
|
require "./config/*"
|
||||||
require "./services/*"
|
require "./lib/*"
|
||||||
|
require "./models/*"
|
||||||
|
require "./serializers/*"
|
||||||
|
|
||||||
require "./routes"
|
require "./routes"
|
||||||
|
|
||||||
error 500 { |env| {"status" => 500, "error" => "Internal Server Error"}.to_json }
|
error 500 { |env| {"status" => 500, "error" => "Internal Server Error"}.to_json }
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
module Pa
|
module App
|
||||||
VERSION = "0.1.0"
|
VERSION = "0.1.0"
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user