use jennifer for db interface

This commit is contained in:
jane 2021-07-24 17:17:10 -04:00
parent 840959ec25
commit 24acfa5846
28 changed files with 575 additions and 18 deletions

2
.gitignore vendored
View file

@ -132,3 +132,5 @@ backend/config.json
/bin/
/.shards/
*.dwarf
Makefile
.vscode/

View 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
View file

@ -0,0 +1,2 @@
require "./initializers/**"
require "../src/models/*"

View 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

View 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)

View file

@ -0,0 +1,3 @@
I18n.load_path += ["./config/locales"]
I18n.init

View file

View 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

View file

@ -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

View 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
View 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
View 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
View 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

View file

@ -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

View file

@ -1,9 +0,0 @@
require "./spec_helper"
describe Backend do
# TODO: Write tests
it "works" do
false.should eq(true)
end
end

View file

@ -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.

View 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

View file

@ -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

View file

View file

View file

View file

View 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

View 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

View 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

View 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

View file

@ -0,0 +1,5 @@
require "jennifer"
require "redis"
module DatabaseCaching
end

View file

@ -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": [