use jennifer for db interface
This commit is contained in:
parent
840959ec25
commit
24acfa5846
28 changed files with 575 additions and 18 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -132,3 +132,5 @@ backend/config.json
|
|||
/bin/
|
||||
/.shards/
|
||||
*.dwarf
|
||||
Makefile
|
||||
.vscode/
|
||||
|
|
9
backend/config.json.example
Normal file
9
backend/config.json.example
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"secret": "TEST_SECRET",
|
||||
"port": 8080,
|
||||
"db_url": "postgres://postgres:@127.0.0.1/todo",
|
||||
"mail_host": "smtp.migadu.com",
|
||||
"mail_port": 465,
|
||||
"mail_username": "",
|
||||
"mail_password": ""
|
||||
}
|
2
backend/config/config.cr
Normal file
2
backend/config/config.cr
Normal file
|
@ -0,0 +1,2 @@
|
|||
require "./initializers/**"
|
||||
require "../src/models/*"
|
16
backend/config/database.yml
Normal file
16
backend/config/database.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
default: &default
|
||||
host: localhost
|
||||
user: postgres
|
||||
adapter: postgres
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
db: todo_dev
|
||||
|
||||
test:
|
||||
<<: *default
|
||||
db: todo_dev
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
db: todo
|
12
backend/config/initializers/database.cr
Normal file
12
backend/config/initializers/database.cr
Normal file
|
@ -0,0 +1,12 @@
|
|||
require "jennifer"
|
||||
require "jennifer/adapter/postgres"
|
||||
|
||||
APP_ENV = ENV["APP_ENV"]? || "development"
|
||||
|
||||
Jennifer::Config.configure do |conf|
|
||||
conf.read("config/database.yml", APP_ENV)
|
||||
conf.from_uri(ENV["DATABASE_URI"]) if ENV.has_key?("DATABASE_URI")
|
||||
conf.logger.level = APP_ENV == "development" ? Log::Severity::Debug : Log::Severity::Info
|
||||
end
|
||||
|
||||
Log.setup "db", :debug, Log::IOBackend.new(formatter: Jennifer::Adapter::DBFormatter)
|
3
backend/config/initializers/zzz_i18n.cr
Normal file
3
backend/config/initializers/zzz_i18n.cr
Normal file
|
@ -0,0 +1,3 @@
|
|||
I18n.load_path += ["./config/locales"]
|
||||
|
||||
I18n.init
|
0
backend/config/locales/en.yml
Normal file
0
backend/config/locales/en.yml
Normal file
19
backend/db/migrations/20210723170518920_create_users.cr
Normal file
19
backend/db/migrations/20210723170518920_create_users.cr
Normal file
|
@ -0,0 +1,19 @@
|
|||
require "jennifer"
|
||||
|
||||
class CreateUsers < Jennifer::Migration::Base
|
||||
def up
|
||||
create_table :users do |t|
|
||||
t.string :id, {:primary => true}
|
||||
t.string :email
|
||||
t.bool :discord_only_account
|
||||
t.string :discord_id, {:null => true}
|
||||
t.string :password_hash, {:null => true}
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :users if table_exists? :users
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
require "jennifer"
|
||||
|
||||
class CreateUnverifiedusers < Jennifer::Migration::Base
|
||||
def up
|
||||
create_table :unverifiedusers do |t|
|
||||
t.string :id, {:primary => true}
|
||||
t.string :email
|
||||
t.bool :discord_only_account
|
||||
t.string :discord_id, {:null => true}
|
||||
t.string :password_hash, {:null => true}
|
||||
t.string :verification_token
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :unverifiedusers if table_exists? :unverifiedusers
|
||||
end
|
||||
end
|
20
backend/db/migrations/20210723171731912_create_todos.cr
Normal file
20
backend/db/migrations/20210723171731912_create_todos.cr
Normal file
|
@ -0,0 +1,20 @@
|
|||
require "jennifer"
|
||||
|
||||
class CreateTodos < Jennifer::Migration::Base
|
||||
def up
|
||||
create_table :todos do |t|
|
||||
t.string :id, {:primary => true}
|
||||
t.string :userid # remember: trying to insert a column named the same as
|
||||
# another model will make the database error :)
|
||||
t.text :content
|
||||
t.string :tags, {:array => true, :null => true}
|
||||
t.bool :complete, {:null => true}
|
||||
t.date_time :deadline, {:null => true}
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :todos if table_exists? :todos
|
||||
end
|
||||
end
|
152
backend/db/structure.sql
Normal file
152
backend/db/structure.sql
Normal file
|
@ -0,0 +1,152 @@
|
|||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 13.3
|
||||
-- Dumped by pg_dump version 13.3
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
|
||||
--
|
||||
-- Name: migration_versions; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.migration_versions (
|
||||
id integer NOT NULL,
|
||||
version character varying(17) NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.migration_versions OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: migration_versions_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.migration_versions_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE public.migration_versions_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: migration_versions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.migration_versions_id_seq OWNED BY public.migration_versions.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: todos; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.todos (
|
||||
id character varying(254) NOT NULL,
|
||||
userid character varying(254),
|
||||
content text,
|
||||
tags character varying(254)[],
|
||||
complete boolean,
|
||||
deadline timestamp without time zone,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.todos OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: unverifiedusers; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.unverifiedusers (
|
||||
id character varying(254) NOT NULL,
|
||||
email character varying(254),
|
||||
discord_only_account boolean,
|
||||
discord_id character varying(254),
|
||||
password_hash character varying(254),
|
||||
verification_token character varying(254),
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.unverifiedusers OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: users; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.users (
|
||||
id character varying(254) NOT NULL,
|
||||
email character varying(254),
|
||||
discord_only_account boolean,
|
||||
discord_id character varying(254),
|
||||
password_hash character varying(254),
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.users OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: migration_versions id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.migration_versions ALTER COLUMN id SET DEFAULT nextval('public.migration_versions_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: migration_versions migration_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.migration_versions
|
||||
ADD CONSTRAINT migration_versions_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: todos todos_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.todos
|
||||
ADD CONSTRAINT todos_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: unverifiedusers unverifiedusers_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.unverifiedusers
|
||||
ADD CONSTRAINT unverifiedusers_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users
|
||||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
13
backend/sam.cr
Normal file
13
backend/sam.cr
Normal file
|
@ -0,0 +1,13 @@
|
|||
require "./config/*"
|
||||
require "sam"
|
||||
require "./db/migrations/*"
|
||||
|
||||
load_dependencies "jennifer"
|
||||
|
||||
# Here you can define your tasks
|
||||
# desc "with description to be used by help command"
|
||||
# task "test" do
|
||||
# puts "ping"
|
||||
# end
|
||||
|
||||
Sam.help
|
78
backend/shard.lock
Normal file
78
backend/shard.lock
Normal file
|
@ -0,0 +1,78 @@
|
|||
version: 2.0
|
||||
shards:
|
||||
ameba:
|
||||
git: https://github.com/crystal-ameba/ameba.git
|
||||
version: 0.14.3
|
||||
|
||||
backtracer:
|
||||
git: https://github.com/sija/backtracer.cr.git
|
||||
version: 1.2.1
|
||||
|
||||
base62:
|
||||
git: https://github.com/janeptrv/base62.cr.git
|
||||
version: 0.1.3
|
||||
|
||||
db:
|
||||
git: https://github.com/crystal-lang/crystal-db.git
|
||||
version: 0.10.1
|
||||
|
||||
email:
|
||||
git: https://github.com/arcage/crystal-email.git
|
||||
version: 0.6.3
|
||||
|
||||
exception_page:
|
||||
git: https://github.com/crystal-loot/exception_page.git
|
||||
version: 0.2.0
|
||||
|
||||
i18n:
|
||||
git: https://github.com/techmagister/i18n.cr.git
|
||||
version: 0.3.1+git.commit.b323291b772c97bc1661888eb9e82dadb722acaa
|
||||
|
||||
ifrit:
|
||||
git: https://github.com/imdrasil/ifrit.git
|
||||
version: 0.1.3
|
||||
|
||||
inflector:
|
||||
git: https://github.com/phoffer/inflector.cr.git
|
||||
version: 1.0.0
|
||||
|
||||
jennifer:
|
||||
git: https://github.com/imdrasil/jennifer.cr.git
|
||||
version: 0.11.0
|
||||
|
||||
kemal:
|
||||
git: https://gitdab.com/luna/kemal.git
|
||||
version: 1.0.0+git.commit.bba5bef50506f7572db9fcdeb107c65709bf1244
|
||||
|
||||
kilt:
|
||||
git: https://github.com/jeromegn/kilt.git
|
||||
version: 0.6.1
|
||||
|
||||
ksuid:
|
||||
git: https://github.com/janeptrv/ksuid.cr.git
|
||||
version: 0.5.2
|
||||
|
||||
pg:
|
||||
git: https://github.com/will/crystal-pg.git
|
||||
version: 0.23.2
|
||||
|
||||
pool:
|
||||
git: https://github.com/ysbaddaden/pool.git
|
||||
version: 0.2.4
|
||||
|
||||
radix:
|
||||
git: https://github.com/luislavena/radix.git
|
||||
version: 0.4.1
|
||||
|
||||
redis:
|
||||
git: https://github.com/stefanwille/crystal-redis.git
|
||||
version: 2.8.0
|
||||
|
||||
sam:
|
||||
git: https://github.com/imdrasil/sam.cr.git
|
||||
version: 0.4.1
|
||||
|
||||
spec-kemal:
|
||||
git: https://gitdab.com/luna/spec-kemal.git
|
||||
version: 1.0.0+git.commit.e4765ff11d66d705438b9c79e77665d85e4ef8f4
|
||||
|
|
@ -11,3 +11,27 @@ targets:
|
|||
crystal: 1.0.0
|
||||
|
||||
license: MIT
|
||||
|
||||
dependencies:
|
||||
spec-kemal:
|
||||
git: https://gitdab.com/luna/spec-kemal.git
|
||||
kemal:
|
||||
git: https://gitdab.com/luna/kemal.git
|
||||
ksuid:
|
||||
github: janeptrv/ksuid.cr
|
||||
email:
|
||||
github: arcage/crystal-email
|
||||
pg:
|
||||
github: will/crystal-pg
|
||||
redis:
|
||||
github: stefanwille/crystal-redis
|
||||
jennifer:
|
||||
github: imdrasil/jennifer.cr
|
||||
version: "~> 0.11.0"
|
||||
sam:
|
||||
github: imdrasil/sam.cr
|
||||
|
||||
development_dependencies:
|
||||
ameba:
|
||||
github: crystal-ameba/ameba
|
||||
version: ~> 0.14.0
|
|
@ -1,9 +0,0 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe Backend do
|
||||
# TODO: Write tests
|
||||
|
||||
it "works" do
|
||||
false.should eq(true)
|
||||
end
|
||||
end
|
|
@ -1,2 +1,6 @@
|
|||
require "spec"
|
||||
require "../src/backend"
|
||||
|
||||
# note: we cannot spec backend.cr
|
||||
# because kemal will start automatically.
|
||||
# that's fine, because there's nothing of note there
|
||||
# anyways.
|
||||
|
|
61
backend/spec/utils/config_spec.cr
Normal file
61
backend/spec/utils/config_spec.cr
Normal file
|
@ -0,0 +1,61 @@
|
|||
require "../spec_helper"
|
||||
require "../../src/utils/config"
|
||||
|
||||
describe Config do
|
||||
before_each {
|
||||
Config.load_config
|
||||
}
|
||||
it "secret is present" do
|
||||
secret = Config.get_config_value("secret")
|
||||
secret.is_a?(JSON::Any).should be_true
|
||||
if secret.is_a?(JSON::Any)
|
||||
secret.as_s.empty?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
it "port is present" do
|
||||
port = Config.get_config_value("port")
|
||||
port.is_a?(JSON::Any).should be_true
|
||||
if port.is_a?(JSON::Any)
|
||||
port.as_i.is_a?(Int).should be_true
|
||||
port.as_i.should_not eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
it "database url is present" do
|
||||
url = Config.get_config_value("db_url")
|
||||
url.is_a?(JSON::Any).should be_true
|
||||
if url.is_a?(JSON::Any)
|
||||
url.as_s.empty?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
it "mail host and port are present" do
|
||||
host = Config.get_config_value("mail_host")
|
||||
host.is_a?(JSON::Any).should be_true
|
||||
if host.is_a?(JSON::Any)
|
||||
host.as_s.empty?.should be_false
|
||||
end
|
||||
|
||||
port = Config.get_config_value("mail_port")
|
||||
port.is_a?(JSON::Any).should be_true
|
||||
if port.is_a?(JSON::Any)
|
||||
port.as_i.is_a?(Int).should be_true
|
||||
port.as_i.should_not eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
it "mail username and password are present" do
|
||||
uname = Config.get_config_value("mail_username")
|
||||
uname.is_a?(JSON::Any).should be_true
|
||||
if uname.is_a?(JSON::Any)
|
||||
uname.as_s.empty?.should be_false
|
||||
end
|
||||
|
||||
pword = Config.get_config_value("mail_password")
|
||||
pword.is_a?(JSON::Any).should be_true
|
||||
if pword.is_a?(JSON::Any)
|
||||
pword.as_s.empty?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,32 @@
|
|||
# TODO: Write documentation for `Backend`
|
||||
module Backend
|
||||
VERSION = "0.1.0"
|
||||
require "kemal"
|
||||
require "log"
|
||||
require "./utils/config"
|
||||
require "./endpoints/user"
|
||||
|
||||
# TODO: Put your code here
|
||||
if !Config.is_loaded
|
||||
puts "loading config"
|
||||
Config.load_config
|
||||
end
|
||||
|
||||
serve_static false
|
||||
|
||||
# replacement for the expressjs/sequelize backend of todo.
|
||||
# because javascript sucks.
|
||||
module Backend
|
||||
VERSION = "0.0.1"
|
||||
end
|
||||
|
||||
get "/api/hello" do
|
||||
"Hello"
|
||||
end
|
||||
|
||||
# this is a slightly less hilarious way to get the integer value of something
|
||||
# because now i am using JSON::Any. but i am going to keep this comment
|
||||
# because i want to.
|
||||
port = Config.get_config_value("port")
|
||||
port_to_use = port.is_a?(JSON::Any) ? port.as_i : 8000
|
||||
|
||||
Kemal.run do |config|
|
||||
server = config.server.not_nil!
|
||||
server.bind_tcp "0.0.0.0", port_to_use
|
||||
end
|
||||
|
|
0
backend/src/endpoints/auth.cr
Normal file
0
backend/src/endpoints/auth.cr
Normal file
0
backend/src/endpoints/register.cr
Normal file
0
backend/src/endpoints/register.cr
Normal file
0
backend/src/endpoints/todo.cr
Normal file
0
backend/src/endpoints/todo.cr
Normal file
0
backend/src/endpoints/user.cr
Normal file
0
backend/src/endpoints/user.cr
Normal file
16
backend/src/models/todo.cr
Normal file
16
backend/src/models/todo.cr
Normal file
|
@ -0,0 +1,16 @@
|
|||
require "jennifer"
|
||||
|
||||
class Todo < Jennifer::Model::Base
|
||||
with_timestamps
|
||||
|
||||
mapping(
|
||||
id: {type: String, primary: true},
|
||||
userid: String,
|
||||
content: String,
|
||||
tags: Array(String)?,
|
||||
complete: Bool?,
|
||||
deadline: Time?,
|
||||
created_at: Time?,
|
||||
updated_at: Time?,
|
||||
)
|
||||
end
|
16
backend/src/models/unverified_user.cr
Normal file
16
backend/src/models/unverified_user.cr
Normal file
|
@ -0,0 +1,16 @@
|
|||
require "jennifer"
|
||||
|
||||
class UnverifiedUser < Jennifer::Model::Base
|
||||
with_timestamps
|
||||
|
||||
mapping(
|
||||
id: {type: String, primary: true},
|
||||
email: String,
|
||||
discord_only_account: {type: Bool, default: false},
|
||||
discord_id: String?,
|
||||
password_hash: String?,
|
||||
verification_token: String,
|
||||
created_at: Time?,
|
||||
updated_at: Time?,
|
||||
)
|
||||
end
|
15
backend/src/models/user.cr
Normal file
15
backend/src/models/user.cr
Normal file
|
@ -0,0 +1,15 @@
|
|||
require "jennifer"
|
||||
|
||||
class User < Jennifer::Model::Base
|
||||
with_timestamps
|
||||
|
||||
mapping(
|
||||
id: {type: String, primary: true},
|
||||
email: String,
|
||||
discord_only_account: {type: Bool, default: false},
|
||||
discord_id: String?,
|
||||
password_hash: String?,
|
||||
created_at: Time?,
|
||||
updated_at: Time?,
|
||||
)
|
||||
end
|
53
backend/src/utils/config.cr
Normal file
53
backend/src/utils/config.cr
Normal file
|
@ -0,0 +1,53 @@
|
|||
require "file"
|
||||
require "json"
|
||||
require "log"
|
||||
|
||||
class ConfigInstance
|
||||
@@config = {} of String => JSON::Any
|
||||
@@loaded = false
|
||||
|
||||
def config
|
||||
@@config
|
||||
end
|
||||
|
||||
def config=(new_value)
|
||||
@@config = new_value
|
||||
end
|
||||
|
||||
def loaded
|
||||
@@loaded
|
||||
end
|
||||
|
||||
def loaded=(new_value)
|
||||
@@loaded = new_value
|
||||
end
|
||||
end
|
||||
|
||||
Instance = ConfigInstance.new
|
||||
|
||||
module Config
|
||||
extend self
|
||||
|
||||
def get_config_value(key) : JSON::Any | Nil
|
||||
Log.debug { "looking for #{key}" }
|
||||
if Instance.config.has_key? key
|
||||
Instance.config[key]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def is_loaded : Bool
|
||||
Instance.loaded
|
||||
end
|
||||
|
||||
def load_config : Nil
|
||||
loaded_config = File.open("config.json") do |file|
|
||||
Hash(String, JSON::Any).from_json(file)
|
||||
end
|
||||
Log.debug { "loaded config is #{loaded_config}" }
|
||||
Instance.config = loaded_config
|
||||
Instance.loaded = true
|
||||
Log.debug { "instance config is #{Instance.config}" }
|
||||
end
|
||||
end
|
5
backend/src/utils/database_caching.cr
Normal file
5
backend/src/utils/database_caching.cr
Normal file
|
@ -0,0 +1,5 @@
|
|||
require "jennifer"
|
||||
require "redis"
|
||||
|
||||
module DatabaseCaching
|
||||
end
|
|
@ -24,10 +24,10 @@
|
|||
"web-vitals": "^1.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"start": "BROWSER=none react-scripts start",
|
||||
"build": "BROWSER=none react-scripts build",
|
||||
"test": "BROWSER=none react-scripts test",
|
||||
"eject": "BROWSER=none react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
|
Loading…
Reference in a new issue