1
0
mirror of https://github.com/danbee/chess synced 2025-03-04 08:39:06 +00:00

Compare commits

...

45 Commits

Author SHA1 Message Date
205d5a5a24 Add back @session_options 2023-10-04 15:33:52 -05:00
87504064e7 WIP: Game info 2023-10-04 12:09:06 -05:00
3543ccdcae Fix layout issue caused by live component container 2023-10-04 12:09:06 -05:00
1ea842d0ab Bunch of fixes 2023-10-04 12:09:04 -05:00
f5c35de8d8 Clients subscribe to the game to get updates 2023-10-04 12:08:46 -05:00
bbc1838d7e Tidy 2023-10-04 12:08:46 -05:00
04e00e530b Now we're correctly handling moves 2023-10-04 12:08:44 -05:00
7e2c841b59 Correctly choose pieces 2023-10-04 12:07:11 -05:00
b01403cbe4 These variables aren't used 2023-10-04 12:07:11 -05:00
dea6198e30 WIP 2023-10-04 12:07:07 -05:00
d2074ade11 Install LiveView 2023-10-04 12:06:12 -05:00
2f561c28fe Few rendering fix ups 2023-10-04 12:04:19 -05:00
34551788c7 Render the initial board in Phoenix 2023-10-04 12:04:19 -05:00
a26f044822 Correct build command 2023-02-05 12:46:37 -06:00
a750756187 Working dart_sass version 2023-02-05 12:44:18 -06:00
662efda6b1 Install Yarn properly 2023-02-05 12:18:20 -06:00
6f7b087b82 Install NodeJS and Yarn 2023-02-05 11:59:58 -06:00
b1451535ca Add assets step 2023-02-04 23:35:05 -06:00
e4fc3a1b6f Install ChromeDriver 2023-02-04 23:18:22 -06:00
6cecc3005c Force install of Chrome 2023-02-04 23:02:35 -06:00
c19f586d13 Update Apt 2023-02-04 23:01:22 -06:00
f5641150a0 Rename pg-db to db 2023-02-04 22:59:58 -06:00
f0ed129028 Add files for Codeship Pro 2023-02-04 22:58:09 -06:00
1a9358d5e0 Install Chromedriver for Codeship 2023-02-04 21:40:21 -06:00
8f683dd73f Mix format 2023-02-04 21:35:56 -06:00
69f05a1c5d Fix tests 2023-02-04 21:33:48 -06:00
01471967c8 Update deps and format code 2023-02-02 19:32:42 -06:00
5cd457bc62 Switch default domain 2023-01-30 12:19:36 -06:00
27f5fc6730 Switch to Config module 2023-01-30 12:19:22 -06:00
d6ca73e4bd Will this work? 2023-01-30 11:52:33 -06:00
9909abd17f NodeJS version 14.20.1 2023-01-30 11:52:33 -06:00
6eb2a53695 Is this the right format? 2022-10-12 20:41:50 -05:00
9b0c75c2ce Update NodeJS version 2022-10-12 20:40:23 -05:00
7bf2312106 Update Elixir 2022-10-12 20:40:11 -05:00
1de19ae6c9 Mix buildpacks 2022-02-15 21:28:49 -06:00
fc83a1e16d 23.3.2 2022-02-15 21:07:16 -06:00
909d04f2a1 Add Gigalixir asset deploy script 2022-02-15 21:05:07 -06:00
6c51aa113f OTP? 2022-02-15 21:04:29 -06:00
a893dd3e85 Upgrade to latest Elixir/Erlang 2022-02-15 21:00:29 -06:00
c3e1206b58 Add :ssl to extra applications 2022-02-15 18:42:58 -06:00
14952a5d6c Add Runtime config 2022-02-15 18:37:53 -06:00
a83ee57e7b Switch to ESBuild and Dart-Sass 2022-02-15 18:37:33 -06:00
cfa472e13a Compatible Node version 2022-02-15 18:36:27 -06:00
1d7fb6c87b Migrate SASS to use new math.div function 2022-02-15 18:36:10 -06:00
d56502a001 Upgrade Phoenix 2022-02-15 12:46:08 -06:00
108 changed files with 2541 additions and 4671 deletions

View File

@ -1,4 +1,3 @@
https://github.com/gigalixir/gigalixir-buildpack-clean-cache.git
https://github.com/HashNuke/heroku-buildpack-elixir
https://github.com/gjaldon/heroku-buildpack-phoenix-static
https://github.com/gigalixir/gigalixir-buildpack-distillery.git
https://github.com/gigalixir/gigalixir-buildpack-mix.git

3
.formatter.exs Normal file
View File

@ -0,0 +1,3 @@
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

View File

@ -1,4 +1,4 @@
elixir 1.11.3
python 2.7.14
nodejs 10.16.0
erlang 23.2.3
elixir 1.14.1
python 3.9.1
nodejs 16.14.0
erlang 24.2.1

42
Dockerfile Normal file
View File

@ -0,0 +1,42 @@
FROM --platform=linux/amd64 elixir:1.14.1
# Create a directory for your application code and set it as the WORKDIR. All following commands will be run in this directory.
RUN mkdir /app
WORKDIR /app
# Update package repo
RUN apt-get update
# Install Chrome
RUN wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
apt-get -y install ./google-chrome-stable_current_amd64.deb
# Install ChromeDriver
RUN CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \
mkdir -p /opt/chromedriver-$CHROMEDRIVER_VERSION && \
curl -sS -o /tmp/chromedriver_linux64.zip http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip && \
unzip -qq /tmp/chromedriver_linux64.zip -d /opt/chromedriver-$CHROMEDRIVER_VERSION && \
rm /tmp/chromedriver_linux64.zip && \
chmod +x /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver && \
ln -fs /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver /usr/local/bin/chromedriver
# Install NodeJS
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
apt-get install nodejs
# Install Yarn
RUN npm install --global yarn
# Install Rebar and Hex
RUN mix local.rebar --force && mix local.hex --force
# Install the Phoenix Mix archive
RUN mix archive.install --sha512 2a2f5167f5ea30f314500da449dbd8cfb4b6986cc27197c82fa4cc328798814f89a4dbe0183a5f213faed3587e8133ce99c1fab74cf1597978a270bdcc7bf789 --force https://github.com/phoenixframework/archives/raw/master/phx_new.ez
# COPY mix.exs and mix.lock and install dependencies before adding the full code so the cache only
# gets invalidated when dependencies are changed
COPY mix.exs mix.lock ./
RUN mix deps.get
# Copy the app source code into the image
COPY ./ /app

View File

@ -8,13 +8,16 @@
width: 1.5%;
}
.board__container {
grid-area: board;
}
.board {
background: $background-color;
border-collapse: unset;
border-radius: 2.8%;
border-spacing: 1px;
color: $foreground-color;
grid-area: board;
height: var(--board-size);
padding: calc(var(--board-size) / 20);
position: relative;
@ -58,6 +61,14 @@
display: flex;
flex-direction: row;
}
.board__body {
flex-direction: column-reverse;
}
.board__row {
flex-direction: row;
}
}
.board--player-is-black {
@ -70,20 +81,45 @@
display: flex;
flex-direction: row-reverse;
}
.board__body {
flex-direction: column;
}
.board__row {
flex-direction: row-reverse;
}
}
.board__body {
background: $background-color;
border: 0.25rem solid $foreground-color;
border-radius: calc(var(--board-size) / 100);
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: repeat(8, 1fr);
display: flex;
flex-direction: column;
height: 100%;
padding: 1%;
width: 100%;
}
.board__row {
display: flex;
flex-grow: 1;
@include odd-between(1, 8) {
.square {
@include odd-between(1, 8) { @extend %square--black; }
@include even-between(1, 8) { @extend %square--white; }
}
}
@include even-between(1, 8) {
.square {
@include odd-between(1, 8) { @extend %square--white; }
@include even-between(1, 8) { @extend %square--black; }
}
}
}
.board__rank-labels {
display: none;
height: 90%;

View File

@ -1,3 +1,5 @@
@use "sass:math";
.opponent-finder__result {
border-bottom: 1px dotted $black;
border-bottom-left-radius: $base-border-radius;
@ -5,14 +7,14 @@
border-left: 1px dotted $black;
border-right: 1px dotted $black;
margin-top: -$small-spacing;
padding: $small-spacing / 3;
padding: math.div($small-spacing, 3);
}
.opponent-finder__result-item {
border-radius: $base-border-radius / 2;
border-radius: $base-border-radius * 0.5;
cursor: pointer;
display: block;
padding: $small-spacing / 3 $base-spacing / 2;
padding: math.div($small-spacing, 3) $base-spacing * 0.5;
text-decoration: none;
&:hover,

View File

@ -57,36 +57,13 @@
border-radius: 4%;
margin: 0.5px;
position: relative;
flex-grow: 1;
// This is to ensure the squares can be clicked on in PhantomJS
// TODO: Figure out why we need this
min-height: 20px;
min-width: 20px;
@include odd-between(1, 8) { @extend %square--white; }
@include even-between(1, 8) { @extend %square--black; }
@include odd-between(9, 16) { @extend %square--black; }
@include even-between(9, 16) { @extend %square--white; }
@include odd-between(17, 24) { @extend %square--white; }
@include even-between(17, 24) { @extend %square--black; }
@include odd-between(25, 32) { @extend %square--black; }
@include even-between(25, 32) { @extend %square--white; }
@include odd-between(33, 40) { @extend %square--white; }
@include even-between(33, 40) { @extend %square--black; }
@include odd-between(41, 48) { @extend %square--black; }
@include even-between(41, 48) { @extend %square--white; }
@include odd-between(49, 56) { @extend %square--white; }
@include even-between(49, 56) { @extend %square--black; }
@include odd-between(57, 64) { @extend %square--black; }
@include even-between(57, 64) { @extend %square--white; }
&::before {
background-position: center;
background-repeat: no-repeat;

View File

@ -1,3 +1,5 @@
@use "sass:math";
$_form-background-color: $background-color;
fieldset {
@ -9,14 +11,14 @@ fieldset {
legend {
font-weight: $heavy-font-weight;
margin-bottom: $small-spacing / 2;
margin-bottom: $small-spacing * 0.5;
padding: 0;
}
label {
display: block;
font-weight: $heavy-font-weight;
margin-bottom: $small-spacing / 2;
margin-bottom: $small-spacing * 0.5;
}
input,
@ -35,7 +37,7 @@ textarea {
box-sizing: border-box;
color: $foreground-color;
margin-bottom: $small-spacing;
padding: $base-spacing / 3;
padding: math.div($base-spacing, 3);
transition: border-color $base-duration $base-timing;
width: 100%;

View File

@ -1,7 +1,7 @@
$base-border-radius: 0.5rem;
$base-spacing: 2rem;
$small-spacing: $base-spacing / 2;
$x-small-spacing: $small-spacing / 2;
$small-spacing: $base-spacing * 0.5;
$x-small-spacing: $small-spacing * 0.5;
$base-z-index: 0;
$container-width: 90%;

View File

@ -130,7 +130,7 @@
/// @content [Write the style you want to apply to the children, and it will be added within the @content directive]
/// @param {number} $num - id of the child
@mixin middle($num) {
&:nth-child(#{round($num / 2)}) {
&:nth-child(#{round($num * 0.5)}) {
@content;
}
}

View File

@ -67,6 +67,8 @@
///
/// @require {function} _fetch-bourbon-setting
@use "sass:math";
@function modular-scale(
$increment,
$value: _fetch-bourbon-setting("modular-scale-base"),
@ -78,7 +80,7 @@
// scale $v2 to just above $v1
@while $v2 > $v1 {
$v2: ($v2 / $ratio); // will be off-by-1
$v2: math.div($v2, $ratio); // will be off-by-1
}
@while $v2 < $v1 {
$v2: ($v2 * $ratio); // will fix off-by-1
@ -102,15 +104,15 @@
@if $increment < 0 {
// adjust $v2 to just below $v1
@if $double-stranded {
$v2: ($v2 / $ratio);
$v2: math.div($v2, $ratio);
}
@for $i from $increment through -1 {
@if $double-stranded and ($v1 / $ratio) < $v2 {
@if $double-stranded and math.div($v1, $ratio) < $v2 {
$value: $v2;
$v2: ($v2 / $ratio);
$v2: math.div($v2, $ratio);
} @else {
$v1: ($v1 / $ratio);
$v1: math.div($v1, $ratio);
$value: $v1;
}
}

View File

@ -12,6 +12,8 @@
/// // Output
/// $dimension: 10;
@use "sass:math";
@function strip-unit($value) {
@return ($value / ($value * 0 + 1));
@return math.div($value, $value * 0 + 1);
}

View File

@ -55,25 +55,25 @@
@if $direction == "up" {
border-color: transparent transparent $color;
border-width: 0 ($width / 2) $height;
border-width: 0 ($width * 0.5) $height;
} @else if $direction == "up-right" {
border-color: transparent $color transparent transparent;
border-width: 0 $width $width 0;
} @else if $direction == "right" {
border-color: transparent transparent transparent $color;
border-width: ($height / 2) 0 ($height / 2) $width;
border-width: ($height * 0.5) 0 ($height * 0.5) $width;
} @else if $direction == "down-right" {
border-color: transparent transparent $color;
border-width: 0 0 $width $width;
} @else if $direction == "down" {
border-color: $color transparent transparent;
border-width: $height ($width / 2) 0;
border-width: $height ($width * 0.5) 0;
} @else if $direction == "down-left" {
border-color: transparent transparent transparent $color;
border-width: $width 0 0 $width;
} @else if $direction == "left" {
border-color: transparent $color transparent transparent;
border-width: ($height / 2) $width ($height / 2) 0;
border-width: ($height * 0.5) $width ($height * 0.5) 0;
} @else if $direction == "up-left" {
border-color: $color transparent transparent;
border-width: $width $width 0 0;

View File

@ -19,13 +19,15 @@
///
/// @access private
@use "sass:math";
@function _contrast-ratio($color-1, $color-2) {
$-local-lightness-1: _lightness($color-1) + 0.05;
$-local-lightness-2: _lightness($color-2) + 0.05;
@if $-local-lightness-1 > $-local-lightness-2 {
@return $-local-lightness-1 / $-local-lightness-2;
@return math.div($-local-lightness-1, $-local-lightness-2);
} @else {
@return $-local-lightness-2 / $-local-lightness-1;
@return math.div($-local-lightness-2, $-local-lightness-1);
}
}

View File

@ -11,11 +11,13 @@
///
/// @access private
@use "sass:math";
@function _gamma($channel) {
@if $channel < 0.03928 {
@return $channel / 12.92;
@return math.div($channel, 12.92);
} @else {
$c: ($channel + 0.055) / 1.055;
@return (133 * $c * $c * $c + 155 * $c * $c) / 288;
$c: math.div($channel + 0.055, 1.055);
@return math.div(133 * $c * $c * $c + 155 * $c * $c, 288);
}
}

View File

@ -11,14 +11,16 @@
///
/// @access private
@use "sass:math";
@function _lightness($hex-color) {
$-local-red-raw: red(rgba($hex-color, 1));
$-local-green-raw: green(rgba($hex-color, 1));
$-local-blue-raw: blue(rgba($hex-color, 1));
$-local-red: _gamma($-local-red-raw / 255);
$-local-green: _gamma($-local-green-raw / 255);
$-local-blue: _gamma($-local-blue-raw / 255);
$-local-red: _gamma(math.div($-local-red-raw, 255));
$-local-green: _gamma(math.div($-local-green-raw, 255));
$-local-blue: _gamma(math.div($-local-blue-raw, 255));
@return $-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722;
}

View File

@ -1,5 +1,20 @@
"use strict";
import {Socket} from "phoenix";
import LiveSocket from "phoenix_live_view";
let csrfToken = document
.querySelector("meta[name='csrf-token']").getAttribute("content");
let liveSocket = new LiveSocket(
"/live",
Socket,
{params: {_csrf_token: csrfToken}}
);
liveSocket.connect()
window.liveSocket = liveSocket
import "@babel/polyfill";
import "phoenix_html";
@ -7,8 +22,6 @@ import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import css from "../css/app.scss";
import Game from "./components/game";
import OpponentFinder from "./components/opponent-finder";
import chessBoardReducer from "./reducers/chess-board";

View File

@ -3,18 +3,19 @@
"description": " ",
"license": "MIT",
"scripts": {
"deploy": "webpack --mode production",
"watch": "webpack --mode development --watch-stdin"
"deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild"
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"axios": "^0.21.0",
"classnames": "^2.3.1",
"esbuild": "^0.14.21",
"gettext.js": "^1.0.0",
"immutable": "^3.8.2",
"lodash": "^4.17.21",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix": "^1.7.0-rc.2",
"phoenix_html": "^3.2.0",
"phoenix_live_view": "^0.18.11",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.4",
@ -23,18 +24,7 @@
"url-loader": "^4.1.1"
},
"devDependencies": {
"@babel/core": "^7.14.6",
"@babel/preset-env": "^7.14.7",
"@babel/preset-react": "^7.14.5",
"babel-loader": "^8.2.2",
"copy-webpack-plugin": "^9.0.0",
"css-loader": "^5.2.6",
"mini-css-extract-plugin": "^1.6.0",
"node-sass": "^6.0.0",
"optimize-css-assets-webpack-plugin": "^6.0.1",
"sass-loader": "^12.1.0",
"terser-webpack-plugin": "^5.1.3",
"webpack": "^5.40.0",
"webpack-cli": "^4.7.2"
"mini-css-extract-plugin": "^1.6.0"
}
}

View File

@ -1,56 +0,0 @@
const path = require("path");
const glob = require("glob");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = (env, options) => {
const devMode = options.mode !== "production";
return {
optimization: {
minimizer: [new TerserPlugin(), new OptimizeCSSAssetsPlugin({})],
},
entry: {
app: glob.sync("./vendor/**/*.js").concat(["./js/app.js"]),
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "../priv/static/js"),
publicPath: "/js/",
},
devtool: devMode ? "eval-cheap-module-source-map" : undefined,
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
{
test: /\.s?css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
{
test: /\.(png|jpg|gif|svg)$/i,
use: [
{
loader: "url-loader",
options: {
limit: 8192,
name: "[name].[hash:7].[ext]",
},
},
],
},
],
},
plugins: [
new MiniCssExtractPlugin({ filename: "../css/app.css" }),
new CopyWebpackPlugin({ patterns: [{ from: "static/", to: "../" }] }),
],
};
};

734
assets/yarn-error.log Normal file
View File

@ -0,0 +1,734 @@
Arguments:
/Users/danbarber/.asdf/installs/nodejs/16.14.0/bin/node /Users/danbarber/.asdf/installs/yarn/1.22.10/bin/yarn.js add phoenix phoenix_html phoenix_live_view
PATH:
/Users/danbarber/.asdf/plugins/nodejs/shims:/Users/danbarber/.asdf/installs/nodejs/16.14.0/bin:/Users/danbarber/.asdf/installs/yarn/1.22.10/bin:/Users/danbarber/.bin:/usr/local/share/npm/bin:.git/safe/../../bin:/Users/danbarber/.asdf/shims:/opt/homebrew/bin/../opt/asdf/libexec/bin:/usr/local/sbin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/usr/local/zfs/bin:/opt/X11/bin:/Library/Apple/usr/bin:/Applications/Wireshark.app/Contents/MacOS:/Applications/kitty.app/Contents/MacOS:/Users/danbarber/.fzf/bin
Yarn version:
1.22.10
Node version:
16.14.0
Platform:
darwin arm64
Trace:
Error: ENOENT: no such file or directory, open '/Users/danbarber/Library/Caches/Yarn/v6/npm-esbuild-linux-arm64-0.14.21-e05599ea6253b58394157da162d856f3ead62f9e-integrity/node_modules/esbuild-linux-arm64/.yarn-metadata.json'
npm manifest:
{
"repository": {},
"description": " ",
"license": "MIT",
"scripts": {
"deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild"
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"axios": "^0.21.0",
"classnames": "^2.3.1",
"esbuild": "^0.14.21",
"gettext.js": "^1.0.0",
"immutable": "^3.8.2",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.4",
"redux": "^4.1.0",
"redux-watch": "^1.2.0",
"url-loader": "^4.1.1"
},
"devDependencies": {
"css-loader": "^5.2.6",
"mini-css-extract-plugin": "^1.6.0"
}
}
yarn manifest:
No manifest
Lockfile:
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/polyfill@^7.12.1":
version "7.12.1"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96"
integrity sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==
dependencies:
core-js "^2.6.5"
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
version "7.14.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
dependencies:
regenerator-runtime "^0.13.4"
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/json-schema@^7.0.6":
version "7.0.7"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/react-redux@^7.1.16":
version "7.1.16"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react@*":
version "17.0.11"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.1"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-styles@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
integrity sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=
axios@^0.21.0:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
chalk@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
integrity sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=
dependencies:
ansi-styles "~1.0.0"
has-color "~0.1.0"
strip-ansi "~0.1.0"
classnames@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
colorette@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
core-js@^2.6.5:
version "2.6.12"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
css-loader@^5.2.6:
version "5.2.6"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.6.tgz#c3c82ab77fea1f360e587d871a6811f4450cc8d1"
integrity sha512-0wyN5vXMQZu6BvjbrPdUJvkCzGEO24HC7IS7nW4llc6BBFC+zwR9CKtYGv63Puzsg10L/o12inMY5/2ByzfD6w==
dependencies:
icss-utils "^5.1.0"
loader-utils "^2.0.0"
postcss "^8.2.15"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.1.0"
schema-utils "^3.0.0"
semver "^7.3.5"
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
csstype@^3.0.2:
version "3.0.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
encoding@^0.1.11:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
esbuild-android-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.21.tgz#8842d0c3b7c81fbe2dc46ddb416ffd6eb822184b"
integrity sha512-Bqgld1TY0wZv8TqiQmVxQFgYzz8ZmyzT7clXBDZFkOOdRybzsnj8AZuK1pwcLVA7Ya6XncHgJqIao7NFd3s0RQ==
esbuild-darwin-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.21.tgz#ec7df02ad88ecf7f8fc23a3ed7917e07dea0c9c9"
integrity sha512-j+Eg+e13djzyYINVvAbOo2/zvZ2DivuJJTaBrJnJHSD7kUNuGHRkHoSfFjbI80KHkn091w350wdmXDNSgRjfYQ==
esbuild-darwin-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.21.tgz#0c2a977edec1ef54097ee56a911518c820d4e5e4"
integrity sha512-nDNTKWDPI0RuoPj5BhcSB2z5EmZJJAyRtZLIjyXSqSpAyoB8eyAKXl4lB8U2P78Fnh4Lh1le/fmpewXE04JhBQ==
esbuild-freebsd-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.21.tgz#f5b5fc1d031286c3a0949d1bda7db774b7d0404e"
integrity sha512-zIurkCHXhxELiDZtLGiexi8t8onQc2LtuE+S7457H/pP0g0MLRKMrsn/IN4LDkNe6lvBjuoZZi2OfelOHn831g==
esbuild-freebsd-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.21.tgz#a05cab908013e4992b31a675850b8c44eb468c0c"
integrity sha512-wdxMmkJfbwcN+q85MpeUEamVZ40FNsBa9mPq8tAszDn8TRT2HoJvVRADPIIBa9SWWwlDChIMjkDKAnS3KS/sPA==
esbuild-linux-32@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.21.tgz#638d244cc58b951f447addb4bade628d126ef84b"
integrity sha512-fmxvyzOPPh2xiEHojpCeIQP6pXcoKsWbz3ryDDIKLOsk4xp3GbpHIEAWP0xTeuhEbendmvBDVKbAVv3PnODXLg==
esbuild-linux-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.21.tgz#8eb634abee928be7e35b985fafbfef2f2e31397f"
integrity sha512-edZyNOv1ql+kpmlzdqzzDjRQYls+tSyi4QFi+PdBhATJFUqHsnNELWA9vMSzAaInPOEaVUTA5Ml28XFChcy4DA==
esbuild-linux-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.21.tgz#e05599ea6253b58394157da162d856f3ead62f9e"
integrity sha512-t5qxRkq4zdQC0zXpzSB2bTtfLgOvR0C6BXYaRE/6/k8/4SrkZcTZBeNu+xGvwCU4b5dU9ST9pwIWkK6T1grS8g==
esbuild-linux-arm@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.21.tgz#1ae1078231cf689d3ba894a32d3723c0be9b91fd"
integrity sha512-aSU5pUueK6afqmLQsbU+QcFBT62L+4G9hHMJDHWfxgid6hzhSmfRH9U/f+ymvxsSTr/HFRU4y7ox8ZyhlVl98w==
esbuild-linux-mips64le@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.21.tgz#f05be62d126764e99b37edcac5bb49b78c7a8890"
integrity sha512-jLZLQGCNlUsmIHtGqNvBs3zN+7a4D9ckf0JZ+jQTwHdZJ1SgV9mAjbB980OFo66LoY+WeM7t3WEnq3FjI1zw4A==
esbuild-linux-ppc64le@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.21.tgz#592c98d82dad7982268ef8deed858c4566f07ab1"
integrity sha512-4TWxpK391en2UBUw6GSrukToTDu6lL9vkm3Ll40HrI08WG3qcnJu7bl8e1+GzelDsiw1QmfAY/nNvJ6iaHRpCQ==
esbuild-linux-riscv64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.21.tgz#0db7bd6f10d8f9afea973a7d6bf87b449b864b7b"
integrity sha512-fElngqOaOfTsF+u+oetDLHsPG74vB2ZaGZUqmGefAJn3a5z9Z2pNa4WpVbbKgHpaAAy5tWM1m1sbGohj6Ki6+Q==
esbuild-linux-s390x@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.21.tgz#254a9354d34c9d1b41a3e21d2ec9269cbbb2c5df"
integrity sha512-brleZ6R5fYv0qQ7ZBwenQmP6i9TdvJCB092c/3D3pTLQHBGHJb5zWgKxOeS7bdHzmLy6a6W7GbFk6QKpjyD6QA==
esbuild-netbsd-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.21.tgz#4cb783d060b02bf3b897a9a12cce2b3b547726f8"
integrity sha512-nCEgsLCQ8RoFWVV8pVI+kX66ICwbPP/M9vEa0NJGIEB/Vs5sVGMqkf67oln90XNSkbc0bPBDuo4G6FxlF7PN8g==
esbuild-openbsd-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.21.tgz#f886b93feefddbe573528fa4b421c9c6e2bc969b"
integrity sha512-h9zLMyVD0T73MDTVYIb/qUTokwI6EJH9O6wESuTNq6+XpMSr6C5aYZ4fvFKdNELW+Xsod+yDS2hV2JTUAbFrLA==
esbuild-sunos-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.21.tgz#3829e4d57d4cb6950837fe90b0b67cdfb37cf13a"
integrity sha512-Kl+7Cot32qd9oqpLdB1tEGXEkjBlijrIxMJ0+vlDFaqsODutif25on0IZlFxEBtL2Gosd4p5WCV1U7UskNQfXA==
esbuild-windows-32@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.21.tgz#b858a22d1a82e53cdc59310cd56294133f7a95e7"
integrity sha512-V7vnTq67xPBUCk/9UtlolmQ798Ecjdr1ZoI1vcSgw7M82aSSt0eZdP6bh5KAFZU8pxDcx3qoHyWQfHYr11f22A==
esbuild-windows-64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.21.tgz#7bb5a027d5720cf9caf18a4bedd11327208f1f12"
integrity sha512-kDgHjKOHwjfJDCyRGELzVxiP/RBJBTA+wyspf78MTTJQkyPuxH2vChReNdWc+dU2S4gIZFHMdP1Qrl/k22ZmaA==
esbuild-windows-arm64@0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.21.tgz#25df54521ad602c826b262ea2e7cc1fe80f5c2f5"
integrity sha512-8Sbo0zpzgwWrwjQYLmHF78f7E2xg5Ve63bjB2ng3V2aManilnnTGaliq2snYg+NOX60+hEvJHRdVnuIAHW0lVw==
esbuild@^0.14.21:
version "0.14.21"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.21.tgz#b3e05f900f1c4394f596d60d63d9816468f0f671"
integrity sha512-7WEoNMBJdLN993dr9h0CpFHPRc3yFZD+EAVY9lg6syJJ12gc5fHq8d75QRExuhnMkT2DaRiIKFThRvDWP+fO+A==
optionalDependencies:
esbuild-android-arm64 "0.14.21"
esbuild-darwin-64 "0.14.21"
esbuild-darwin-arm64 "0.14.21"
esbuild-freebsd-64 "0.14.21"
esbuild-freebsd-arm64 "0.14.21"
esbuild-linux-32 "0.14.21"
esbuild-linux-64 "0.14.21"
esbuild-linux-arm "0.14.21"
esbuild-linux-arm64 "0.14.21"
esbuild-linux-mips64le "0.14.21"
esbuild-linux-ppc64le "0.14.21"
esbuild-linux-riscv64 "0.14.21"
esbuild-linux-s390x "0.14.21"
esbuild-netbsd-64 "0.14.21"
esbuild-openbsd-64 "0.14.21"
esbuild-sunos-64 "0.14.21"
esbuild-windows-32 "0.14.21"
esbuild-windows-64 "0.14.21"
esbuild-windows-arm64 "0.14.21"
fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
follow-redirects@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
gettext-parser@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/gettext-parser/-/gettext-parser-1.1.0.tgz#2c5a6638d893934b9b55037d0ad82cb7004b2679"
integrity sha1-LFpmONiTk0ubVQN9CtgstwBLJnk=
dependencies:
encoding "^0.1.11"
gettext.js@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/gettext.js/-/gettext.js-1.0.0.tgz#7fefb01512c134759c51166ab4d3db26a585ae1a"
integrity sha512-cpnxNL5C9SlD7ms/NSCuGsQdaVQmwCYn9MILWpYjSPMAkX4aD/5/qC+QgH4GCRY0OMEcSiVBsqgWMEoTcETggQ==
dependencies:
po2json "^0.4.0"
has-color@~0.1.0:
version "0.1.7"
resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f"
integrity sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
iconv-lite@^0.6.2:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
icss-utils@^5.0.0, icss-utils@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
immutable@^3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
dependencies:
minimist "^1.2.5"
loader-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
json5 "^2.1.2"
lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
mime-db@1.48.0:
version "1.48.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d"
integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==
mime-types@^2.1.27:
version "2.1.31"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b"
integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==
dependencies:
mime-db "1.48.0"
mini-css-extract-plugin@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.0.tgz#b4db2525af2624899ed64a23b0016e0036411893"
integrity sha512-nPFKI7NSy6uONUo9yn2hIfb9vyYvkFu95qki0e21DQ9uaqNKDP15DGpK0KnV6wDroWxPHtExrdEwx/yDQ8nVRw==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
webpack-sources "^1.1.0"
minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
nanoid@^3.1.23:
version "3.1.23"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
nomnom@1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7"
integrity sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=
dependencies:
chalk "~0.4.0"
underscore "~1.6.0"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
object-path@^0.11.5:
version "0.11.5"
resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.5.tgz#d4e3cf19601a5140a55a16ad712019a9c50b577a"
integrity sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg==
"phoenix@file:../deps/phoenix":
version "1.6.6"
"phoenix_html@file:../deps/phoenix_html":
<<<<<<< HEAD
version "3.2.0"
=======
version "2.14.3"
"phoenix_live_view@file:../deps/phoenix_live_view":
version "0.15.3"
picomatch@^2.2.1, picomatch@^2.2.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
"phoenix_live_view@file:../deps/phoenix_live_view":
version "0.15.3"
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
dependencies:
pinkie "^2.0.0"
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
pkg-dir@^4.1.0, pkg-dir@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
dependencies:
find-up "^4.0.0"
>>>>>>> 7af5f8c (Install LiveView)
po2json@^0.4.0:
version "0.4.5"
resolved "https://registry.yarnpkg.com/po2json/-/po2json-0.4.5.tgz#47bb2952da32d58a1be2f256a598eebc0b745118"
integrity sha1-R7spUtoy1Yob4vJWpZjuvAt0URg=
dependencies:
gettext-parser "1.1.0"
nomnom "1.8.1"
postcss-modules-extract-imports@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
postcss-modules-local-by-default@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser "^6.0.2"
postcss-value-parser "^4.1.0"
postcss-modules-scope@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
dependencies:
postcss-selector-parser "^6.0.4"
postcss-modules-values@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
version "6.0.6"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea"
integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss@^8.2.15:
version "8.3.5"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709"
integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.23"
source-map-js "^0.6.2"
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
react-dom@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler "^0.20.2"
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-redux@^7.2.4:
version "7.2.4"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
dependencies:
"@babel/runtime" "^7.12.1"
"@types/react-redux" "^7.1.16"
hoist-non-react-statics "^3.3.2"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^16.13.1"
react@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
redux-watch@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/redux-watch/-/redux-watch-1.2.0.tgz#b3a745e15855ef72db0e06a60a4b2fe3676d79a7"
integrity sha512-Ws4Q+e5zFGMyy1H709c1Ws8apSd6MqoJRIzBDHbI4nikome/IZWVTYXdQNz+VJxPjyX/h2E+lYEo41fXgjCF8g==
dependencies:
object-path "^0.11.5"
redux@^4.0.0, redux@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4"
integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
dependencies:
"@babel/runtime" "^7.9.2"
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
dependencies:
"@types/json-schema" "^7.0.6"
ajv "^6.12.5"
ajv-keywords "^3.5.2"
semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
source-list-map@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
source-map-js@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
strip-ansi@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
integrity sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=
underscore@~1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
url-loader@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2"
integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==
dependencies:
loader-utils "^2.0.0"
mime-types "^2.1.27"
schema-utils "^3.0.0"
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
webpack-sources@^1.1.0:
version "1.4.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
dependencies:
source-list-map "^2.0.0"
source-map "~0.6.1"
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==

File diff suppressed because it is too large Load Diff

View File

@ -3,5 +3,5 @@
echo "Building assets"
cd assets
yarn install
yarn run build
yarn run deploy
cd ..

View File

@ -6,8 +6,8 @@ source /dev/stdin <<< "$(curl -sSL https://raw.githubusercontent.com/codeship/sc
echo "Installing Elixir"
source /dev/stdin <<< "$(curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/languages/elixir.sh)"
echo "Installing PhantomJS"
curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/packages/phantomjs.sh | bash -s
echo "Installing ChromeDriver"
curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/packages/chromedriver.sh | bash -s
echo "Installing NodeJS"
nvm install $NODE_VERSION

18
codeship-services.yml Normal file
View File

@ -0,0 +1,18 @@
# Docker Compose-like syntax, see here for details:
# https://documentation.codeship.com/pro/builds-and-configuration/services/
app:
build:
image: danbee/chess
dockerfile: Dockerfile
environment:
MIX_ENV: test
PGHOST: db
PGUSER: postgres
PGPASSWORD: password
depends_on:
- db
db:
image: healthcheck/postgres:alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password

9
codeship-steps.yml Normal file
View File

@ -0,0 +1,9 @@
# This is where we define the test steps that would be run in a Codeship Pro build.
# The build passes as long as every test step returns with a non-zero exit code.
# See here for more: https://documentation.codeship.com/pro/builds-and-configuration/steps/
- name: build_assets
command: bin/codeship_assets
service: app
- name: run_mix_test
command: /bin/bash -c 'mix ecto.create && mix test'
service: app

View File

@ -3,7 +3,7 @@
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config
import Config
config :phoenix, :json_library, Jason
@ -16,7 +16,8 @@ config :chess, ChessWeb.Endpoint,
url: [host: "localhost"],
secret_key_base: "iiTDTKorCWTFoeBgAkr35XZp22cNIM2RsmnHiHdzKAuSHXUGXx42z7lawAwiu1B1",
render_errors: [view: ChessWeb.ErrorView, accepts: ~w(html json)],
pubsub_server: Chess.PubSub
pubsub_server: Chess.PubSub,
live_view: [signing_salt: "R3JjvjiRi64kjFCHqCJCfk7EY8hohfadgLqO/VUFRO8="]
# Configures Elixir's Logger
config :logger, :console,
@ -30,8 +31,27 @@ config :chess, Chess.Auth.Guardian,
secret_key: "vd2vXkrYTTFKSKmNMoS2/Hk4Fxn8BkyzsVArRkxJazdQ3mr6bI4YgAC6f8ODiWlM"
config :formulator,
translate_error_module: ChessWeb.ErrorHelpers
validate: false,
translate_error_module: ChessWeb.ErrorHelpers
# Configure esbuild (the version is required)
config :esbuild,
version: "0.17.5",
default: [
args:
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --loader:.js=jsx),
cd: Path.expand("../assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
# Configure dart_sass
config :dart_sass,
version: "1.58.0",
default: [
args: ~w(css/app.scss ../priv/static/assets/app.css),
cd: Path.expand("../assets", __DIR__)
]
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"
import_config "#{config_env()}.exs"

View File

@ -1,4 +1,4 @@
use Mix.Config
import Config
# For development, we disable any cache and enable
# debugging and code reloading.
@ -10,16 +10,7 @@ config :chess, ChessWeb.Endpoint,
http: [port: 4000],
debug_errors: true,
code_reloader: true,
check_origin: false,
watchers: [
node: [
"node_modules/webpack/bin/webpack.js",
"--mode",
"development",
"--watch",
cd: Path.expand("../assets", __DIR__)
]
]
check_origin: false
# Watch static and templates for browser reloading.
config :chess, ChessWeb.Endpoint,

View File

@ -1,8 +1,8 @@
use Mix.Config
import Config
config :chess, ChessWeb.Endpoint,
cache_static_manifest: "priv/static/cache_manifest.json",
check_origin: ["https://chess.danbarber.me", "https://64squares.club"],
check_origin: ["https://chess.danbee.in", "https://64squares.club"],
http: [port: {:system, "PORT"}],
root: "./assets",
secret_key_base: System.get_env("SECRET_KEY_BASE"),

70
config/runtime.exs Normal file
View File

@ -0,0 +1,70 @@
import Config
# config/runtime.exs is executed for all environments, including
# during releases. It is executed after compilation and before the
# system starts, so it is typically used to load production configuration
# and secrets from environment variables or elsewhere. Do not define
# any compile-time configuration in here, as it won't be applied.
# The block below contains prod specific runtime configuration.
# Start the phoenix server if environment is set and running in a release
if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do
config :chess, ChessWeb.Endpoint, server: true
end
if config_env() == :prod do
# The secret key base is used to sign/encrypt cookies and other secrets.
# A default value is used in config/dev.exs and config/test.exs but you
# want to use a different value for prod and you most likely don't want
# to check this value into version control, so we use an environment
# variable instead.
secret_key_base =
System.get_env("SECRET_KEY_BASE") ||
raise """
environment variable SECRET_KEY_BASE is missing.
You can generate one by calling: mix phx.gen.secret
"""
host = System.get_env("PHX_HOST") || "example.com"
port = String.to_integer(System.get_env("PORT") || "4000")
config :chess, ChessWeb.Endpoint,
url: [host: host, port: 443],
http: [
# Enable IPv6 and bind on all interfaces.
# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
# See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
# for details about using IPv6 vs IPv4 and loopback vs public addresses.
ip: {0, 0, 0, 0, 0, 0, 0, 0},
port: port
],
secret_key_base: secret_key_base
# ## Using releases
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start each relevant endpoint:
#
# config :<%= @web_app_name %>, <%= @endpoint_module %>, server: true
#
# Then you can assemble a release by calling `mix release`.
# See `mix help release` for more information.<%= if @mailer do %>
# ## Configuring the mailer
#
# In production you need to configure the mailer to use a different adapter.
# Also, you may need to configure the Swoosh API client of your choice if you
# are not using SMTP. Here is an example of the configuration:
#
# config :<%= @app_name %>, <%= @app_module %>.Mailer,
# adapter: Swoosh.Adapters.Mailgun,
# api_key: System.get_env("MAILGUN_API_KEY"),
# domain: System.get_env("MAILGUN_DOMAIN")
#
# For this example you need include a HTTP client required by Swoosh API client.
# Swoosh supports Hackney and Finch out of the box:
#
# config :swoosh, :api_client, Swoosh.ApiClient.Hackney
#
# See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.<% end %>
end

View File

@ -1,4 +1,4 @@
use Mix.Config
import Config
# We don't run a server during test. If one is required,
# you can enable the server option below.
@ -18,7 +18,7 @@ config :chess, Chess.Mailer, adapter: Bamboo.TestAdapter
config :chess, Chess.Repo,
adapter: Ecto.Adapters.Postgres,
database: "chess_test",
hostname: "localhost",
hostname: System.get_env("PGHOST") || "localhost",
port: System.get_env("POSTGRES_PORT") || "5432",
pool: Ecto.Adapters.SQL.Sandbox

19
docker-compose.yml Normal file
View File

@ -0,0 +1,19 @@
version: '3'
services:
app:
build: .
command: /bin/bash -c 'mix ecto.create && mix phx.server'
ports:
- "4000:4000"
environment:
MIX_ENV: dev
PGHOST: db
PGUSER: postgres
PGPASSWORD: password
links:
- db
db:
image: healthcheck/postgres:alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password

View File

@ -1,5 +1,5 @@
# Erlang version
erlang_version=23.2.3
erlang_version=OTP-24.2.1
# Elixir version
elixir_version=1.11.3
elixir_version=1.14.1

View File

@ -11,7 +11,7 @@ defmodule Chess do
{Phoenix.PubSub, [name: Chess.PubSub, adapter: Phoenix.PubSub.PG2]},
{Chess.Repo, []},
{ChessWeb.Endpoint, []},
{ChessWeb.Presence, []},
{ChessWeb.Presence, []}
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html

View File

@ -4,7 +4,6 @@ defmodule Chess.Auth do
"""
import Ecto.Query, warn: false
alias Comeonin.Argon2
alias Chess.Repo
alias Chess.Store.User
@ -41,11 +40,13 @@ defmodule Chess.Auth do
@doc false
def authenticate_user(email, password) do
query = from u in User,
where: u.email == ^email
query =
from(u in User,
where: u.email == ^email
)
query
|> Repo.one
|> Repo.one()
|> Argon2.check_pass(password)
end
end

View File

@ -12,6 +12,7 @@ defmodule Chess.Auth.ErrorHandler do
|> put_flash(:info, "You must be logged in")
|> redirect(to: "/")
|> halt()
"json" ->
conn
|> put_status(403)

View File

@ -10,8 +10,9 @@ defmodule Chess.Auth.Guardian do
end
def resource_from_claims(claims) do
user = claims["sub"]
|> Auth.get_user!
user =
claims["sub"]
|> Auth.get_user!()
{:ok, user}
end

View File

@ -7,11 +7,11 @@ defmodule Chess.Auth.Pipeline do
module: Chess.Auth.Guardian
# If there is a session token, validate it
plug Guardian.Plug.VerifySession, claims: %{"typ" => "access"}
plug(Guardian.Plug.VerifySession, claims: %{"typ" => "access"})
# If there is an authorization header, validate it
plug Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"}
plug(Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"})
# Load the user if either of the verifications worked
plug Guardian.Plug.LoadResource, allow_blank: true
plug(Guardian.Plug.LoadResource, allow_blank: true)
end

View File

@ -2,8 +2,8 @@ defmodule Chess.Board do
@moduledoc false
def transform(board) do
Enum.map(0..7, fn (rank) ->
Enum.map(0..7, fn (file) ->
Enum.map(0..7, fn rank ->
Enum.map(0..7, fn file ->
board
|> piece({file, rank})
end)
@ -12,7 +12,7 @@ defmodule Chess.Board do
def search(board, %{"type" => type, "colour" => colour}) do
board
|> Enum.filter(fn({_index, piece}) ->
|> Enum.filter(fn {_index, piece} ->
match?(%{"type" => ^type, "colour" => ^colour}, piece)
end)
|> indexes_to_tuples
@ -20,7 +20,7 @@ defmodule Chess.Board do
def search(board, %{"colour" => colour}) do
board
|> Enum.filter(fn({_index, piece}) ->
|> Enum.filter(fn {_index, piece} ->
match?(%{"colour" => ^colour}, piece)
end)
|> indexes_to_tuples
@ -31,9 +31,9 @@ defmodule Chess.Board do
end
def move_piece(board, %{
"from" => [from_file, from_rank],
"to" => [to_file, to_rank]
}) do
"from" => [from_file, from_rank],
"to" => [to_file, to_rank]
}) do
{piece, board} = Map.pop(board, to_index({from_file, from_rank}))
{piece_captured, board} = Map.pop(board, to_index({to_file, to_rank}))
board = Map.put(board, to_index({to_file, to_rank}), piece)
@ -42,8 +42,8 @@ defmodule Chess.Board do
if castling_move?(piece, from_file, to_file) do
board
|> castling_move(%{
"from" => [from_file, from_rank],
"to" => [to_file, to_rank]
from: {from_file, from_rank},
to: {to_file, to_rank}
})
|> Map.get(:board)
else
@ -55,39 +55,40 @@ defmodule Chess.Board do
to: %{"file" => to_file, "rank" => to_rank},
board: board,
piece: piece,
piece_captured: piece_captured,
piece_captured: piece_captured
}
end
def castling_move?(%{"type" => "king"}, 4, to_file) do
to_file == 2 || to_file == 6
end
def castling_move?(_, _, _), do: false
def castling_move(board, %{"from" => [4, rank], "to" => [2, _rank]}) do
def castling_move(board, %{from: {4, rank}, to: {2, _rank}}) do
move_piece(board, %{
"from" => [0, rank],
"to" => [3, rank],
"to" => [3, rank]
})
end
def castling_move(board, %{"from" => [4, rank], "to" => [6, _rank]}) do
move_piece(board, %{
"from" => [7, rank],
"to" => [5, rank],
"to" => [5, rank]
})
end
def default do
%{
"0,7" => %{"type" => "rook", "colour" => "black"},
"0,7" => %{"type" => "rook", "colour" => "black"},
"1,7" => %{"type" => "knight", "colour" => "black"},
"2,7" => %{"type" => "bishop", "colour" => "black"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{"type" => "king", "colour" => "black"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{"type" => "king", "colour" => "black"},
"5,7" => %{"type" => "bishop", "colour" => "black"},
"6,7" => %{"type" => "knight", "colour" => "black"},
"7,7" => %{"type" => "rook", "colour" => "black"},
"7,7" => %{"type" => "rook", "colour" => "black"},
"0,6" => %{"type" => "pawn", "colour" => "black"},
"1,6" => %{"type" => "pawn", "colour" => "black"},
"2,6" => %{"type" => "pawn", "colour" => "black"},
@ -96,7 +97,6 @@ defmodule Chess.Board do
"5,6" => %{"type" => "pawn", "colour" => "black"},
"6,6" => %{"type" => "pawn", "colour" => "black"},
"7,6" => %{"type" => "pawn", "colour" => "black"},
"0,1" => %{"type" => "pawn", "colour" => "white"},
"1,1" => %{"type" => "pawn", "colour" => "white"},
"2,1" => %{"type" => "pawn", "colour" => "white"},
@ -105,15 +105,14 @@ defmodule Chess.Board do
"5,1" => %{"type" => "pawn", "colour" => "white"},
"6,1" => %{"type" => "pawn", "colour" => "white"},
"7,1" => %{"type" => "pawn", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"1,0" => %{"type" => "knight", "colour" => "white"},
"2,0" => %{"type" => "bishop", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"},
"4,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"},
"4,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "bishop", "colour" => "white"},
"6,0" => %{"type" => "knight", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}
"7,0" => %{"type" => "rook", "colour" => "white"}
}
end
@ -123,13 +122,13 @@ defmodule Chess.Board do
defp indexes_to_tuples(list) do
list
|> Enum.map(fn({index, _piece}) -> index_to_tuple(index) end)
|> Enum.map(fn {index, _piece} -> index_to_tuple(index) end)
end
defp index_to_tuple(index) do
index
|> String.split(",")
|> Enum.map(&(String.to_integer(&1)))
|> List.to_tuple
|> Enum.map(&String.to_integer(&1))
|> List.to_tuple()
end
end

View File

@ -12,9 +12,7 @@ defmodule Chess.Emails do
new_email()
|> to(game.opponent)
|> from({"64squares", "games@64squares.club"})
|> subject(
"[64squares] #{game.user.name} has invited you to play a game of chess."
)
|> subject("[64squares] #{game.user.name} has invited you to play a game of chess.")
|> text_body("""
Game link: #{Helpers.game_url(conn, :show, game)}
""")
@ -27,9 +25,7 @@ defmodule Chess.Emails do
new_email()
|> to(opponent)
|> from({"64squares", "games@64squares.club"})
|> subject(
"[64squares] #{user.name} has moved."
)
|> subject("[64squares] #{user.name} has moved.")
|> text_body("""
Game link: #{Helpers.game_url(socket, :show, game)}
""")

View File

@ -14,11 +14,15 @@ defmodule Chess.GameState do
cond do
player_checkmated?(board, colour) ->
"checkmate"
player_stalemated?(board, colour) ->
"stalemate"
king_in_check?(board, colour) ->
"check"
true -> nil
true ->
nil
end
end
@ -36,7 +40,7 @@ defmodule Chess.GameState do
king =
board
|> Board.search(%{"type" => "king", "colour" => colour})
|> List.first
|> List.first()
if is_nil(king) do
raise "There is no #{colour} king!"
@ -49,7 +53,7 @@ defmodule Chess.GameState do
def player_cannot_move?(board, colour) do
board
|> Board.search(%{"colour" => colour})
|> Enum.all?(fn({file, rank}) ->
|> Enum.all?(fn {file, rank} ->
board
|> piece_cannot_move?({file, rank})
end)
@ -62,9 +66,9 @@ defmodule Chess.GameState do
board
|> Moves.available({file, rank})
|> Enum.all?(fn({to_file, to_rank}) ->
|> Enum.all?(fn {to_file, to_rank} ->
board
|> Board.move_piece(%{"from" => [file, rank], "to" => [to_file, to_rank]})
|> Board.move_piece(%{from: {file, rank}, to: {to_file, to_rank}})
|> Map.get(:board)
|> king_in_check?(piece["colour"])
end)

View File

@ -5,7 +5,7 @@ defmodule Chess.MoveList do
def transform(moves) do
moves
|> Enum.map(&(Move.transform(&1)))
|> Enum.map(&Move.transform(&1))
|> Enum.chunk_every(2)
end
end

View File

@ -19,10 +19,10 @@ defmodule Chess.Moves do
game.board
|> Board.move_piece(move_params)
Multi.new
Multi.new()
|> Multi.update(:game, Game.move_changeset(game, params))
|> Multi.insert(:move, Ecto.build_assoc(game, :moves, params))
|> Repo.transaction
|> Repo.transaction()
end
def available(board, {file, rank}, move_list \\ []) do
@ -33,14 +33,19 @@ defmodule Chess.Moves do
case piece do
%{"type" => "pawn"} ->
Pawn.moves(board, {file, rank})
%{"type" => "rook"} ->
Rook.moves(board, {file, rank})
%{"type" => "bishop"} ->
Bishop.moves(board, {file, rank})
%{"type" => "knight"} ->
Knight.moves(board, {file, rank})
%{"type" => "king"} ->
King.moves(board, {file, rank}, move_list)
%{"type" => "queen"} ->
Queen.moves(board, {file, rank})
end

View File

@ -22,13 +22,17 @@ defmodule Chess.Moves.Generator do
defp _moves(_colour, _board, {_file, 0}, {_, -1}), do: []
defp _moves(_colour, _board, {7, _rank}, {1, _}), do: []
defp _moves(_colour, _board, {_file, 7}, {_, 1}), do: []
defp _moves(colour, board, {file, rank}, {fv, rv}) do
next_square = {file + fv, rank + rv}
cond do
can_capture_piece?(colour, board, next_square) ->
[next_square]
obstruction?(colour, board, next_square) ->
[]
true ->
[next_square | _moves(colour, board, next_square, {fv, rv})]
end
@ -36,14 +40,18 @@ defmodule Chess.Moves.Generator do
# Move generation for pieces that follow a pattern
defp _moves(_colour, _board, {_file, _rank}, []), do: []
defp _moves(colour, board, {file, rank}, [{fv, rv} | moves]) do
move_square = {file + fv, rank + rv}
cond do
outside_board?(move_square) ||
obstruction?(colour, board, move_square) ->
obstruction?(colour, board, move_square) ->
_moves(colour, board, {file, rank}, moves)
can_capture_piece?(colour, board, move_square) ->
[move_square | _moves(colour, board, {file, rank}, moves)]
true ->
[move_square | _moves(colour, board, {file, rank}, moves)]
end
@ -66,6 +74,7 @@ defmodule Chess.Moves.Generator do
piece =
board
|> Board.piece({file, rank})
piece && piece["colour"] == colour
end
end

View File

@ -28,7 +28,7 @@ defmodule Chess.Moves.Piece do
end
defp attacked_by_knight?(board, {file, rank}) do
_attacked?(board, {file, rank}, Knight.pattern, "knight")
_attacked?(board, {file, rank}, Knight.pattern(), "knight")
end
defp attacked_by_pawn?(board, {file, rank}) do
@ -46,22 +46,24 @@ defmodule Chess.Moves.Piece do
board
|> Generator.moves({file, rank}, pattern)
Enum.any?(moves, &(match_piece(board, &1, "pawn")))
Enum.any?(moves, &match_piece(board, &1, "pawn"))
end
defp _attacked?(_board, {0, _rank}, {-1, _}, _), do: false
defp _attacked?(_board, {_file, 0}, {_, -1}, _), do: false
defp _attacked?(_board, {7, _rank}, {1, _}, _), do: false
defp _attacked?(_board, {_file, 7}, {_, 1}, _), do: false
defp _attacked?(board, {file, rank}, {fv, rv}, pieces) do
board
|> Generator.moves({file, rank}, {fv, rv})
|> List.last
|> List.last()
|> case do
{file, rank} ->
piece = board["#{file},#{rank}"]
Enum.any?(pieces, &(match?(%{"type" => ^&1}, piece)))
Enum.any?(pieces, &match?(%{"type" => ^&1}, piece))
nil ->
false
end
@ -72,7 +74,7 @@ defmodule Chess.Moves.Piece do
board
|> Generator.moves({file, rank}, pattern)
Enum.any?(moves, &(match_piece(board, &1, piece_type)))
Enum.any?(moves, &match_piece(board, &1, piece_type))
end
defp match_piece(board, {file, rank}, piece_type) do

View File

@ -18,12 +18,13 @@ defmodule Chess.Moves.Pieces.King.Castling do
[]
end
end
def moves(_board, _piece, _move_list), do: []
def _moves(board, _rank, colour, move_list) do
board
|> Board.search(%{"type" => "rook", "colour" => colour})
|> Enum.map(fn ({file, rank}) ->
|> Enum.map(fn {file, rank} ->
case file do
0 -> queen_side_move(board, rank, colour, move_list)
7 -> king_side_move(board, rank, colour, move_list)
@ -35,44 +36,50 @@ defmodule Chess.Moves.Pieces.King.Castling do
defp king_has_moved?(move_list, colour) do
move_list
|> Enum.any?(fn (move) ->
match?(%Move{
piece: %{"type" => "king", "colour" => ^colour}
}, move)
|> Enum.any?(fn move ->
match?(
%Move{
piece: %{"type" => "king", "colour" => ^colour}
},
move
)
end)
end
defp queen_side_move(board, rank, colour, move_list) do
if queen_side_squares_empty?(board, rank) &&
!queen_side_in_check?(board, rank, colour) &&
!rook_has_moved?(0, move_list, colour) do
!queen_side_in_check?(board, rank, colour) &&
!rook_has_moved?(0, move_list, colour) do
{2, rank}
end
end
defp king_side_move(board, rank, colour, move_list) do
if king_side_squares_empty?(board, rank) &&
!king_side_in_check?(board, rank, colour) &&
!rook_has_moved?(7, move_list, colour) do
!king_side_in_check?(board, rank, colour) &&
!rook_has_moved?(7, move_list, colour) do
{6, rank}
end
end
defp rook_has_moved?(file, move_list, colour) do
move_list
|> Enum.any?(fn (move) ->
match?(%Move{
piece: %{"type" => "rook", "colour" => ^colour},
from: %{"file" => ^file},
}, move)
|> Enum.any?(fn move ->
match?(
%Move{
piece: %{"type" => "rook", "colour" => ^colour},
from: %{"file" => ^file}
},
move
)
end)
end
defp queen_side_in_check?(board, rank, colour) do
[{2, rank}, {3, rank}]
|> Enum.any?(fn ({to_file, to_rank}) ->
|> Enum.any?(fn {to_file, to_rank} ->
board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|> Board.move_piece(%{from: {4, rank}, to: {to_file, to_rank}})
|> Map.get(:board)
|> GameState.king_in_check?(colour)
end)
@ -80,9 +87,9 @@ defmodule Chess.Moves.Pieces.King.Castling do
defp king_side_in_check?(board, rank, colour) do
[{5, rank}, {6, rank}]
|> Enum.any?(fn ({to_file, to_rank}) ->
|> Enum.any?(fn {to_file, to_rank} ->
board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|> Board.move_piece(%{from: {4, rank}, to: {to_file, to_rank}})
|> Map.get(:board)
|> GameState.king_in_check?(colour)
end)

View File

@ -37,8 +37,10 @@ defmodule Chess.Moves.Pieces.Pawn do
cond do
obstruction?(board, {file, rank + 1}) ->
[]
rank == 1 ->
[{file, rank + 1} | _moves("white", board, {file, rank + 1})]
true ->
[{file, rank + 1}]
end
@ -48,16 +50,20 @@ defmodule Chess.Moves.Pieces.Pawn do
cond do
obstruction?(board, {file, rank - 1}) ->
[]
rank == 6 ->
[{file, rank - 1} | _moves("black", board, {file, rank - 1})]
true ->
[{file, rank - 1}]
end
end
def _capture_moves(_colour, _board, {_file, _rank}, []), do: []
def _capture_moves(colour, board, {file, rank}, [{fv, rv} | moves]) do
move_square = {file + fv, rank + rv}
if can_capture_piece?(colour, board, move_square) do
[move_square | _capture_moves(colour, board, {file, rank}, moves)]
else

28
lib/chess/release.ex Normal file
View File

@ -0,0 +1,28 @@
defmodule Chess.Release do
@moduledoc """
Used for executing DB release tasks when run in production without Mix
installed.
"""
@app :chess
def migrate do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
load_app()
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
defp repos do
Application.fetch_env!(@app, :ecto_repos)
end
defp load_app do
Application.load(@app)
end
end

View File

@ -23,7 +23,7 @@ defmodule Chess.Repo.Queries do
def opponents(user, query_string) do
user
|> User.opponents
|> User.opponents()
|> User.matches(query_string)
end
end

View File

@ -14,14 +14,14 @@ defmodule Chess.Store.Game do
alias Chess.Store.User
schema "games" do
field :board, :map, default: Board.default()
field :turn, :string, default: "white"
field :state, :string
field(:board, :map, default: Board.default())
field(:turn, :string, default: "white")
field(:state, :string)
belongs_to :user, User
belongs_to :opponent, User, references: :id
belongs_to(:user, User)
belongs_to(:opponent, User, references: :id)
has_many :moves, Move
has_many(:moves, Move)
timestamps()
end
@ -55,15 +55,17 @@ defmodule Chess.Store.Game do
end
def for_user_id(user_id) do
from game in Game,
from(game in Game,
where: game.user_id == ^user_id,
or_where: game.opponent_id == ^user_id
)
end
def check_game_state(changeset) do
changeset
|> put_change(
:state, GameState.state(changeset.changes.board, changeset.changes.turn)
:state,
GameState.state(changeset.changes.board, changeset.changes.turn)
)
end
@ -78,6 +80,7 @@ defmodule Chess.Store.Game do
changeset
end
end
def validate_king_in_check(changeset, _, _), do: changeset
def ordered(query) do

View File

@ -8,13 +8,13 @@ defmodule Chess.Store.Move do
alias Chess.Store.Game
schema "moves" do
field :from, :map
field :to, :map
field(:from, :map)
field(:to, :map)
field :piece, :map
field :piece_captured, :map
field(:piece, :map)
field(:piece_captured, :map)
belongs_to :game, Game
belongs_to(:game, Game)
timestamps()
end
@ -31,7 +31,7 @@ defmodule Chess.Store.Move do
piece: move.piece,
piece_captured: move.piece_captured,
from: <<97 + move.from["file"], 49 + move.from["rank"]>>,
to: <<97 + move.to["file"], 49 + move.to["rank"]>>,
to: <<97 + move.to["file"], 49 + move.to["rank"]>>
}
end

View File

@ -6,16 +6,14 @@ defmodule Chess.Store.User do
import Ecto.Changeset
import Ecto.Query
alias Comeonin.Argon2
schema "users" do
field :name, :string
field :email, :string
field :password, :string, virtual: true
field :password_hash, :string
field(:name, :string)
field(:email, :string)
field(:password, :string, virtual: true)
field(:password_hash, :string)
has_many :games, Chess.Store.Game
has_many :games_as_opponent, Chess.Store.Game, foreign_key: :opponent_id
has_many(:games, Chess.Store.Game)
has_many(:games_as_opponent, Chess.Store.Game, foreign_key: :opponent_id)
timestamps()
end
@ -53,26 +51,32 @@ defmodule Chess.Store.User do
end
def find_by_name(name) do
from user in __MODULE__,
from(user in __MODULE__,
where: user.name == ^name
)
end
def opponents(user) do
from user in __MODULE__,
from(user in __MODULE__,
where: user.id != ^user.id
)
end
def matches(query, query_string) do
from user in query,
where: ilike(user.name, ^"%#{query_string}%")
or user.email == ^query_string
from(user in query,
where:
ilike(user.name, ^"%#{query_string}%") or
user.email == ^query_string
)
end
defp hash_password(changeset) do
password = get_change(changeset, :password)
if password do
changeset
|> change(Argon2.add_hash(password))
|> change(password: nil)
else
changeset
end

View File

@ -19,6 +19,7 @@ defmodule ChessWeb do
def controller do
quote do
use Phoenix.Controller, namespace: ChessWeb
import Phoenix.LiveView.Controller
alias Chess.Repo
import Ecto
@ -31,12 +32,14 @@ defmodule ChessWeb do
def view do
quote do
use Phoenix.View, root: "lib/chess_web/templates",
namespace: ChessWeb
use Phoenix.View,
root: "lib/chess_web/templates",
namespace: ChessWeb
# Import convenience functions from controllers
import Phoenix.Controller,
only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
import Phoenix.LiveView.Helpers
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
@ -49,9 +52,45 @@ defmodule ChessWeb do
end
end
def live_view do
quote do
use Phoenix.LiveView,
layout: {ChessWeb.LayoutView, "live.html"}
unquote(view_helpers())
end
end
def live_component do
quote do
use Phoenix.LiveComponent
unquote(view_helpers())
end
end
defp view_helpers do
quote do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
# Import LiveView helpers (live_render, live_component, live_patch, etc)
import Phoenix.LiveView.Helpers
# Import basic rendering functionality (render, render_layout, etc)
import Phoenix.View
import ChessWeb.ErrorHelpers
import ChessWeb.Gettext
alias ChessWeb.Router.Helpers, as: Routes
end
end
def router do
quote do
use Phoenix.Router
import Phoenix.LiveView.Router
end
end

View File

@ -32,7 +32,7 @@ defmodule ChessWeb.GameChannel do
board: Board.transform(game.board),
turn: game.turn,
state: game.state,
moves: MoveList.transform(game.moves),
moves: MoveList.transform(game.moves)
}
socket
@ -57,6 +57,7 @@ defmodule ChessWeb.GameChannel do
update_opponent(socket, game)
{:noreply, socket}
{:error, :game, changeset, _} ->
{message, _} = changeset.errors[:board]
@ -65,21 +66,26 @@ defmodule ChessWeb.GameChannel do
end
def handle_in(
"game:get_available_moves",
%{"square" => [file, rank]},
socket
) do
"game:get_available_moves",
%{"square" => [file, rank]},
socket
) do
game =
socket.assigns.user_id
|> Queries.game_with_moves(socket.assigns.game_id)
moves = Moves.available(game.board, {
String.to_integer(file),
String.to_integer(rank)
}, game.moves)
moves =
Moves.available(
game.board,
{
String.to_integer(file),
String.to_integer(rank)
},
game.moves
)
reply = %{
moves: Enum.map(moves, &(Tuple.to_list(&1)))
moves: Enum.map(moves, &Tuple.to_list(&1))
}
{:reply, {:ok, reply}, socket}
@ -88,27 +94,29 @@ defmodule ChessWeb.GameChannel do
def update_opponent(socket, game) do
opponent_id =
opponent(game, socket.assigns.user_id).id
|> Integer.to_string
|> Integer.to_string()
send_update(socket)
"game:#{game.id}"
|> Presence.list
|> Presence.list()
|> case do
%{^opponent_id => _} ->
nil
_ ->
socket
|> Emails.opponent_moved_email(game)
|> Mailer.deliver_later
|> Mailer.deliver_later()
end
end
def track_presence(socket) do
{:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
user_id: socket.assigns.user_id,
online_at: inspect(System.system_time(:second))
})
{:ok, _} =
Presence.track(socket, socket.assigns.user_id, %{
user_id: socket.assigns.user_id,
online_at: inspect(System.system_time(:second))
})
socket
|> push("presence_state", Presence.list(socket))
@ -116,8 +124,8 @@ defmodule ChessWeb.GameChannel do
def convert_params(%{"from" => from, "to" => to}) do
%{
"from" => Enum.map(from, &(String.to_integer(&1))),
"to" => Enum.map(to, &(String.to_integer(&1))),
"from" => Enum.map(from, &String.to_integer(&1)),
"to" => Enum.map(to, &String.to_integer(&1))
}
end
@ -130,7 +138,7 @@ defmodule ChessWeb.GameChannel do
board: Board.transform(game.board),
turn: game.turn,
state: game.state,
moves: MoveList.transform(game.moves),
moves: MoveList.transform(game.moves)
}
ChessWeb.Endpoint.broadcast("game:#{game.id}", "game:update", payload)

View File

@ -68,6 +68,7 @@ defmodule ChessWeb.Presence do
information, while maintaining the required `:metas` field from the
original presence data.
"""
use Phoenix.Presence, otp_app: :chess,
pubsub_server: Chess.PubSub
use Phoenix.Presence,
otp_app: :chess,
pubsub_server: Chess.PubSub
end

View File

@ -4,7 +4,7 @@ defmodule ChessWeb.UserSocket do
alias Phoenix.Token
## Channels
channel "game:*", ChessWeb.GameChannel
channel("game:*", ChessWeb.GameChannel)
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
@ -21,10 +21,12 @@ defmodule ChessWeb.UserSocket do
case Token.verify(socket, "game socket", token, max_age: 1_209_600) do
{:ok, user_id} ->
{:ok, assign(socket, :user_id, user_id)}
{:error, _reason} ->
:error
end
end
def connect(%{}, _socket), do: :error
# Socket id's are topics that allow you to identify all sockets for a given user:

View File

@ -10,8 +10,8 @@ defmodule ChessWeb.Api.OpponentsController do
conn
|> current_user()
|> Queries.opponents(query_string)
|> Repo.all
|> Repo.all()
render conn, "index.json", %{opponents: opponents}
render(conn, "index.json", %{opponents: opponents})
end
end

View File

@ -15,9 +15,9 @@ defmodule ChessWeb.GameController do
conn
|> current_user()
|> Game.for_user()
|> Game.ordered
|> Game.ordered()
|> preload([:user, :opponent])
|> Repo.all
|> Repo.all()
conn
|> render("index.html", games: games, changeset: changeset)
@ -42,7 +42,7 @@ defmodule ChessWeb.GameController do
|> Repo.preload(:user)
|> Repo.preload(:opponent)
)
|> Mailer.deliver_later
|> Mailer.deliver_later()
conn
|> put_flash(:info, "Game created successfully.")
@ -53,7 +53,7 @@ defmodule ChessWeb.GameController do
conn
|> current_user()
|> User.opponents()
|> Repo.all
|> Repo.all()
conn
|> render("new.html", changeset: changeset, opponents: opponents)

View File

@ -22,6 +22,7 @@ defmodule ChessWeb.PasswordController do
conn
|> put_flash(:info, gettext("Password updated successfully."))
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render(conn, "edit.html", changeset: changeset)
end

View File

@ -22,6 +22,7 @@ defmodule ChessWeb.ProfileController do
conn
|> put_flash(:info, gettext("Profile updated successfully."))
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render(conn, "edit.html", changeset: changeset)
end

View File

@ -18,6 +18,7 @@ defmodule ChessWeb.RegistrationController do
|> Guardian.Plug.sign_in(user)
|> put_flash(:info, "Registered successfully.")
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end

View File

@ -11,17 +11,19 @@ defmodule ChessWeb.SessionController do
end
def create(
conn,
%{"user" => %{"email" => email, "password" => password}}
) do
conn,
%{"user" => %{"email" => email, "password" => password}}
) do
case Auth.authenticate_user(email, password) do
{:ok, user} ->
conn
|> Guardian.Plug.sign_in(user)
|> put_flash(:info, "You are logged in")
|> redirect(to: game_path(conn, :index))
{:error, _error} ->
changeset = User.changeset(%User{})
conn
|> put_flash(:error, "Bad email or password")
|> render("new.html", changeset: changeset)

View File

@ -1,12 +1,20 @@
defmodule ChessWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :chess
if sandbox = Application.get_env(:chess, :sandbox) do
@session_options [
store: :cookie,
key: "_chess_key",
signing_salt: "9LqUhZTU"
]
if sandbox = Application.compile_env(:chess, :sandbox) do
plug(Phoenix.Ecto.SQL.Sandbox, sandbox: sandbox)
end
socket("/socket", ChessWeb.UserSocket)
socket("/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]])
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
@ -45,11 +53,7 @@ defmodule ChessWeb.Endpoint do
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
plug(Plug.Session,
store: :cookie,
key: "_chess_key",
signing_salt: "9LqUhZTU"
)
plug(Plug.Session, @session_options)
plug(ChessWeb.Router)
end

View File

@ -4,57 +4,53 @@ defmodule ChessWeb.Router do
alias Phoenix.Token
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug(:accepts, ["html"])
plug(:fetch_session)
plug(:fetch_live_flash)
plug(:protect_from_forgery)
plug(:put_secure_browser_headers)
end
pipeline :auth do
plug Chess.Auth.Pipeline
plug(Chess.Auth.Pipeline)
end
pipeline :ensure_auth do
plug Guardian.Plug.EnsureAuthenticated
plug :put_user_token
plug(Guardian.Plug.EnsureAuthenticated)
plug(:put_user_token)
end
pipeline :api do
plug :fetch_session
plug :accepts, ["json"]
plug(:fetch_session)
plug(:accepts, ["json"])
end
scope "/", ChessWeb do
pipe_through [:browser, :auth] # Use the default browser stack
# Use the default browser stack
pipe_through([:browser, :auth])
get "/", PageController, :index
resources "/session", SessionController,
only: [:new, :create, :delete], singleton: true
resources "/registration", RegistrationController,
only: [:new, :create], singleton: true
get("/", PageController, :index)
resources("/session", SessionController, only: [:new, :create, :delete], singleton: true)
resources("/registration", RegistrationController, only: [:new, :create], singleton: true)
end
scope "/", ChessWeb do
pipe_through [:browser, :auth, :ensure_auth]
pipe_through([:browser, :auth, :ensure_auth])
resources "/games", GameController,
only: [:index, :new, :create, :show, :delete]
resources "/profile", ProfileController,
only: [:edit, :update], singleton: true
resources "/password", PasswordController,
only: [:edit, :update], singleton: true
resources("/games", GameController, only: [:index, :new, :create, :show, :delete])
resources("/profile", ProfileController, only: [:edit, :update], singleton: true)
resources("/password", PasswordController, only: [:edit, :update], singleton: true)
end
# Other scopes may use custom stacks.
scope "/api", as: :api do
pipe_through [:api, :auth, :ensure_auth]
pipe_through([:api, :auth, :ensure_auth])
resources "/opponents", ChessWeb.Api.OpponentsController, only: [:index]
resources("/opponents", ChessWeb.Api.OpponentsController, only: [:index])
end
if Mix.env == :dev do
forward "/sent_emails", Bamboo.SentEmailViewerPlug
if Mix.env() == :dev do
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
end
defp put_user_token(conn, _) do

View File

@ -0,0 +1,43 @@
<div class="board board--<%= @game.turn %>-to-move board--player-is-<%= player_colour(@user, @game) %>">
<div class="board__rank-labels">
<div class="board__label">1</div>
<div class="board__label">2</div>
<div class="board__label">3</div>
<div class="board__label">4</div>
<div class="board__label">5</div>
<div class="board__label">6</div>
<div class="board__label">7</div>
<div class="board__label">8</div>
</div>
<div class="board__file-labels">
<div class="board__label">a</div>
<div class="board__label">b</div>
<div class="board__label">c</div>
<div class="board__label">d</div>
<div class="board__label">e</div>
<div class="board__label">f</div>
<div class="board__label">g</div>
<div class="board__label">h</div>
</div>
<div class="board__body">
<%= for {rank, row} <- @board do %>
<div class="board__row">
<%= for {file, piece} <- row do %>
<%= render ChessWeb.SquareView,
"square.html",
rank: rank,
file: file,
piece: piece,
selected: {file, rank} == @selected,
available: {file, rank} in @available %>
<% end %>
</div>
<% end %>
</div>
<div class="game-state game-state--<%= @game.state %>">
<%= states[@game.state] %>
</div>
</div>

View File

@ -0,0 +1,6 @@
<div class="game-info">
<p>
Playing <%= opponent(@game, @user.id).name %>
<img class="game-info__opponent-status" src="/images/eye-closed.svg" alt="offline">
</p>
</div>

View File

@ -6,7 +6,7 @@
<tr class="<%= turn_class(@conn, game) %>">
<td>
<span class="games-list__player-indicator">
<img src="images/pawn_<%= player_colour(@conn, game) %>.svg">
<img src="images/pawn_<%= player_colour(current_user(@conn), game) %>.svg">
</span>
<%= link gettext(
"Game with %{name}",

View File

@ -1,2 +1,33 @@
<div id="game" data-game-id="<%= @game.id %>">
<div data-game-id="<%= @game.id %>">
<div class="game-grid">
<%= live_render(
@conn,
ChessWeb.BoardLive,
session: %{"user_id" => current_user(@conn).id, "game_id" => @game.id}
) %>
<%= live_render(
@conn,
ChessWeb.GameInfoLive,
session: %{"user_id" => current_user(@conn).id, "game_id" => @game.id}
) %>
<div class="move-list">
<table class="table table--condensed">
<thead>
<tr>
<th class="move-list__line-number"><span class="visually-hidden">Move no.</span></th>
<th class="move-list__header--white">White</th>
<th class="move-list__header--black">Black</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row" class="move-list__line-number">1.</th>
<td class="move-list__move move-list__move--white">e4</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -4,6 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<%= csrf_meta_tag() %>
<title><%= gettext "64squares" %></title>
<link href="https://fonts.googleapis.com/css?family=Enriqueta:400,700" rel="stylesheet">

View File

@ -1,7 +1,7 @@
<div class="form">
<h2><%= gettext "Password" %></h2>
<%= form_for @changeset, password_path(@conn, :update), [class: "update-password"], fn f -> %>
<%= form_for @changeset, password_path(@conn, :update), [class: "update-password", novalidate: true], fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>

View File

@ -1,7 +1,7 @@
<div class="form">
<h2><%= gettext "Profile" %></h2>
<%= form_for @changeset, profile_path(@conn, :update), [class: "update-profile"], fn f -> %>
<%= form_for @changeset, profile_path(@conn, :update), [class: "update-profile", novalidate: true], fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>

View File

@ -0,0 +1,7 @@
<div
id="f<%= @file %>-r<%= @rank %>"
phx-click="click"
phx-value-rank="<%= @rank %>"
phx-value-file="<%= @file %>"
class="<%= classes(@file, @rank, @piece, @selected, @available) %>"
></div>

View File

@ -3,16 +3,17 @@ defmodule ChessWeb.Api.OpponentsView do
def render("index.json", %{opponents: opponents}) do
%{
opponents: Enum.map(opponents, fn opponent ->
opponent_attrs(opponent)
end)
opponents:
Enum.map(opponents, fn opponent ->
opponent_attrs(opponent)
end)
}
end
def opponent_attrs(opponent) do
%{
id: opponent.id,
name: opponent.name,
name: opponent.name
}
end
end

View File

@ -10,7 +10,7 @@ defmodule ChessWeb.ErrorHelpers do
"""
def error_tag(form, field) do
if error = form.errors[field] do
content_tag :span, translate_error(error), class: "help-block"
content_tag(:span, translate_error(error), class: "help-block")
end
end

View File

@ -2,16 +2,16 @@ defmodule ChessWeb.ErrorView do
use ChessWeb, :view
def render("404.html", _assigns) do
gettext "Page not found"
gettext("Page not found")
end
def render("500.html", _assigns) do
gettext "Internal server error"
gettext("Internal server error")
end
# In case no render clause matches or no
# template is found, let's render it as 500
def template_not_found(_template, assigns) do
render "500.html", assigns
render("500.html", assigns)
end
end

View File

@ -7,8 +7,8 @@ defmodule ChessWeb.GameView do
def won_lost(conn, game) do
if game_over?(game) && game.state == "checkmate" do
your_turn?(conn, game) &&
gettext("You lost") ||
(your_turn?(conn, game) &&
gettext("You lost")) ||
gettext("You won")
end
end
@ -21,9 +21,12 @@ defmodule ChessWeb.GameView do
cond do
GameState.game_over?(game) ->
states()[game.state]
your_turn?(conn, game) ->
gettext("Your turn")
true -> nil
true ->
nil
end
end
@ -34,11 +37,26 @@ defmodule ChessWeb.GameView do
end
def your_turn?(conn, game) do
player_colour(conn, game) == game.turn
conn
|> current_user()
|> player_colour(game) == game.turn
end
def player_colour(conn, game) do
current_user(conn).id == game.user_id && "white" || "black"
(current_user(conn).id == game.user_id && "white") || "black"
end
def files(conn, game) do
ranks(conn, game)
|> Enum.reverse()
end
def ranks(conn, game) do
if game.user_id == current_user(conn).id do
7..0
else
0..7
end
end
def player(game, user_id) do
@ -61,7 +79,7 @@ defmodule ChessWeb.GameView do
%{
"checkmate" => gettext("Checkmate!"),
"stalemate" => gettext("Stalemate"),
"check" => gettext("Check"),
"check" => gettext("Check")
}
end
end

View File

@ -0,0 +1,118 @@
defmodule ChessWeb.BoardLive do
use Phoenix.LiveView, container: {:div, class: "board__container"}
alias Chess.Store.User
alias Chess.Store.Game
alias Chess.Repo
alias Chess.Board
alias Chess.Moves
import Chess.Auth, only: [get_user!: 1]
def render(assigns) do
Phoenix.View.render(ChessWeb.GameView, "board.html", assigns)
end
def mount(_params, %{"user_id" => user_id, "game_id" => game_id}, socket) do
ChessWeb.Endpoint.subscribe("game:#{game_id}")
user = Repo.get!(User, user_id)
game =
Game.for_user(user)
|> Repo.get!(game_id)
{:ok, assign(socket, default_assigns(game, user))}
end
def handle_event("click", %{"rank" => rank, "file" => file}, socket) do
{
:noreply,
socket |> handle_click(String.to_integer(file), String.to_integer(rank))
}
end
defp default_assigns(game, user) do
%{
board: Board.transform(game.board),
game: game,
user: user,
selected: nil,
available: []
}
end
def handle_info(%{event: "move", payload: state}, socket) do
{:noreply, assign(socket, state)}
end
defp handle_click(socket, file, rank) do
game = socket.assigns[:game]
board = game.board
user = socket.assigns[:user]
colour = ChessWeb.GameView.player_colour(user, game)
assigns =
if colour == game.turn do
case socket.assigns do
%{selected: nil} ->
handle_selection(board, colour, file, rank)
_ ->
handle_move(socket.assigns, file, rank)
end
end
assign(socket, assigns)
end
defp handle_selection(board, colour, file, rank) do
case Board.piece(board, {file, rank}) do
%{"colour" => ^colour} ->
[
{:selected, {file, rank}},
{:available, Moves.available(board, {file, rank})}
]
_ ->
[]
end
end
defp handle_move(
%{game: game, available: available, selected: selected},
file,
rank
) do
if {file, rank} in available do
new_game =
game
|> Moves.make_move(%{from: selected, to: {file, rank}})
|> case do
{:ok, %{game: game}} ->
board = Board.transform(game.board)
broadcast_move(game, board)
[
{:selected, nil},
{:available, []},
{:board, board},
{:game, game}
]
end
else
[{:selected, nil}, {:available, []}]
end
end
defp broadcast_move(game, board) do
ChessWeb.Endpoint.broadcast_from(
self(),
"game:#{game.id}",
"move",
%{game: game, board: board}
)
end
end

View File

@ -0,0 +1,21 @@
defmodule ChessWeb.GameInfoLive do
use Phoenix.LiveView
alias Chess.Store.User
alias Chess.Store.Game
alias Chess.Repo
def render(assigns) do
Phoenix.View.render(ChessWeb.GameView, "game_info.html", assigns)
end
def mount(_params, %{"game_id" => game_id, "user_id" => user_id}, socket) do
user = Repo.get!(User, user_id)
game =
Game.for_user(user)
|> Repo.get!(game_id)
{:ok, assign(socket, game: game, user: user)}
end
end

View File

@ -0,0 +1,39 @@
defmodule ChessWeb.SquareView do
use ChessWeb, :view
def classes(_file, _rank, piece, selected, available) do
square_class()
|> add_piece_classes(piece)
|> add_selected_class(selected)
|> add_available_class(available)
|> Enum.join(" ")
end
defp square_class do
["square"]
end
defp add_piece_classes(classes, piece) do
if piece != nil do
classes ++ ["square--#{piece["type"]}", "square--#{piece["colour"]}"]
else
classes
end
end
defp add_selected_class(classes, selected) do
if selected do
classes ++ ["square--selected"]
else
classes
end
end
defp add_available_class(classes, available) do
if available do
classes ++ ["square--available"]
else
classes
end
end
end

41
mix.exs
View File

@ -5,9 +5,9 @@ defmodule Chess.Mixfile do
[
app: :chess,
version: "0.2.0",
elixir: "~> 1.11.3",
elixir: "~> 1.14.1",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
compilers: [:phoenix] ++ Mix.compilers(),
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod,
aliases: aliases(),
@ -21,7 +21,7 @@ defmodule Chess.Mixfile do
def application do
[
mod: {Chess, []},
extra_applications: [:logger]
extra_applications: [:logger, :ssl]
]
end
@ -34,25 +34,29 @@ defmodule Chess.Mixfile do
# Type `mix help deps` for examples and options.
defp deps do
[
{:argon2_elixir, "~> 1.3"},
{:bamboo, "~> 1.0"},
{:comeonin, "~> 4.0"},
{:cowboy, "~> 2.1"},
{:plug_cowboy, "~> 2.1"},
{:argon2_elixir, "~> 3.0"},
{:bamboo, "~> 2.0"},
{:comeonin, "~> 5.0"},
{:cowboy, "~> 2.0"},
{:credo, "~> 1.0", only: [:dev, :test]},
{:dart_sass, "~> 0.5", runtime: Mix.env() == :dev},
{:ecto_sql, "~> 3.0"},
{:formulator, "~> 0.1.6"},
{:gettext, "~> 0.16.0"},
{:guardian, "~> 1.0"},
{:floki, "~> 0.34", only: :test},
{:esbuild, "~> 0.6", runtime: Mix.env() == :dev},
{:formulator, "~> 0.4.0"},
{:gettext, "~> 0.22.0"},
{:guardian, "~> 2.0"},
{:jason, "~> 1.0"},
{:phoenix, "~> 1.5.7"},
{:phoenix, "~> 1.6.0"},
{:phoenix_ecto, "~> 4.0"},
{:phoenix_html, "~> 2.0"},
{:phoenix_html, "~> 3.2.0"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:phoenix_live_view, "~> 0.18"},
{:phoenix_pubsub, "~> 2.0"},
{:postgrex, ">= 0.15.0"},
{:plug_cowboy, "~> 2.0"},
{:postgrex, "~> 0.16.0"},
{:secure_random, "~> 0.5"},
{:wallaby, "~> 0.28.0", [runtime: false, only: :test]}
{:wallaby, "~> 0.30.0", [runtime: false, only: :test]}
]
end
@ -66,7 +70,12 @@ defmodule Chess.Mixfile do
[
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate", "test"]
test: ["ecto.create --quiet", "ecto.migrate", "test"],
"assets.deploy": [
"esbuild default --minify",
"sass default --no-source-map --style=compressed",
"phx.digest"
]
]
end
end

View File

@ -1,53 +1,61 @@
%{
"argon2_elixir": {:hex, :argon2_elixir, "1.3.3", "487ffa071ef78c51d9b16e50ff3cf30cf8204e0aa4bdc8afd3765fdd8195e213", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0ca19822ba4b85fe6dab75d1ed36bd9257a88cc4385bc32be177d861a1f3ec31"},
"bamboo": {:hex, :bamboo, "1.6.0", "adfb583bef028923aae6f22deaea6667290561da1246058556ecaeb0fec5a175", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "454e67feacbc9b6e00553ce1d2fba003c861e0035600d59b09d6159985b17f9b"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
"argon2_elixir": {:hex, :argon2_elixir, "3.0.0", "fd4405f593e77b525a5c667282172dd32772d7c4fa58cdecdaae79d2713b6c5f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "8b753b270af557d51ba13fcdebc0f0ab27a2a6792df72fd5a6cf9cfaffcedc57"},
"bamboo": {:hex, :bamboo, "2.3.0", "d2392a2cabe91edf488553d3c70638b532e8db7b76b84b0a39e3dfe492ffd6fc", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "dd0037e68e108fd04d0e8773921512c940e35d981e097b5793543e3b2f9cd3f6"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm", "d8700a0ca4dbb616c22c9b3f6dd539d88deaafec3efe66869d6370c9a559b3e9"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
"cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
"credo": {:hex, :credo, "1.5.4", "9914180105b438e378e94a844ec3a5088ae5875626fc945b7c1462b41afc3198", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cf51af45eadc0a3f39ba13b56fdac415c91b34f7b7533a13dc13550277141bc4"},
"db_connection": {:hex, :db_connection, "2.3.1", "4c9f3ed1ef37471cbdd2762d6655be11e38193904d9c5c1c9389f1b891a3088e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "abaab61780dde30301d840417890bd9f74131041afd02174cf4e10635b3a63f5"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"},
"dart_sass": {:hex, :dart_sass, "0.5.1", "d45f20a8e324313689fb83287d4702352793ce8c9644bc254155d12656ade8b6", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "24f8a1c67e8b5267c51a33cbe6c0b5ebf12c2c83ace88b5ac04947d676b4ec81"},
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"ecto": {:hex, :ecto, "3.5.5", "48219a991bb86daba6e38a1e64f8cea540cded58950ff38fbc8163e062281a07", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "98dd0e5e1de7f45beca6130d13116eae675db59adfa055fb79612406acf6f6f1"},
"ecto_sql": {:hex, :ecto_sql, "3.5.3", "1964df0305538364b97cc4661a2bd2b6c89d803e66e5655e4e55ff1571943efd", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.5.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2f53592432ce17d3978feb8f43e8dc0705e288b0890caf06d449785f018061c"},
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"},
"elixir_make": {:hex, :elixir_make, "0.7.3", "c37fdae1b52d2cc51069713a58c2314877c1ad40800a57efb213f77b078a460d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "24ada3e3996adbed1fa024ca14995ef2ba3d0d17b678b0f3f2b1f66e6ce2b274"},
"esbuild": {:hex, :esbuild, "0.6.0", "9ba6ead054abd43cb3d7b14946a0cdd1493698ccd8e054e0e5d6286d7f0f509c", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "30f9a05d4a5bab0d3e37398f312f80864e1ee1a081ca09149d06d474318fd040"},
"expo": {:hex, :expo, "0.3.0", "13127c1d5f653b2927f2616a4c9ace5ae372efd67c7c2693b87fd0fdc30c6feb", [:mix], [], "hexpm", "fb3cd4bf012a77bc1608915497dae2ff684a06f0fa633c7afa90c4d72b881823"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"formulator": {:hex, :formulator, "0.1.8", "eb43e1646828edb6614c2cf9c269d55b3d970414684f1e0290734d22c4936af4", [:mix], [{:gettext, ">= 0.11.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.4", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "dae3ff05743db0e31ef722e6e6ef7ed32836a65c422979e4c09b7c2f0494cf2c"},
"gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm", "dd3a7ea5e3e87ee9df29452dd9560709b4c7cc8141537d0b070155038d92bdf1"},
"guardian": {:hex, :guardian, "1.2.1", "bdc8dd3dbf0fb7216cb6f91c11831faa1a64d39cdaed9a611e37f2413e584983", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "723fc404edfb7bd5cba4cd83329b352037f102aa97468f44e58ac7f47c136a98"},
"hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"},
"httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"},
"floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"},
"formulator": {:hex, :formulator, "0.4.0", "43094c3a63e1ee077a01e79425f823d46e031cf7bc9f74045edcda695601efed", [:mix], [{:gettext, ">= 0.11.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.4 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "15c34113d6a797bb03d089a0b976b0cf918d64e24d2096986b61735fffaf1980"},
"gettext": {:hex, :gettext, "0.22.0", "a25d71ec21b1848957d9207b81fd61cb25161688d282d58bdafef74c2270bdc4", [:mix], [{:expo, "~> 0.3.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "cb0675141576f73720c8e49b4f0fd3f2c69f0cd8c218202724d4aebab8c70ace"},
"guardian": {:hex, :guardian, "2.3.1", "2b2d78dc399a7df182d739ddc0e566d88723299bfac20be36255e2d052fd215d", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbe241f9ca1b09fad916ad42d6049d2600bbc688aba5b3c4a6c82592a54274c3"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"jose": {:hex, :jose, "1.11.1", "59da64010c69aad6cde2f5b9248b896b84472e99bd18f246085b7b9fe435dcdb", [:mix, :rebar3], [], "hexpm", "078f6c9fb3cd2f4cfafc972c814261a7d1e8d2b3685c0a76eb87e158efff1ac5"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"},
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.5.7", "2923bb3af924f184459fe4fa4b100bd25fa6468e69b2803dfae82698269aa5e0", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "774cd64417c5a3788414fdbb2be2eb9bcd0c048d9e6ad11a0c1fd67b7c0d0978"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"},
"phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.0", "f35f61c3f959c9a01b36defaa1f0624edd55b87e236b606664a556d6f72fd2e7", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "02c1007ae393f2b76ec61c1a869b1e617179877984678babde131d716f95b582"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"},
"plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"},
"plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"},
"phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.11", "c50eac83dae6b5488859180422dfb27b2c609de87f4aa5b9c926ecd0501cd44f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76c99a0ffb47cd95bf06a917e74f282a603f3e77b00375f3c2dd95110971b102"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"secure_random": {:hex, :secure_random, "0.5.1", "c5532b37c89d175c328f5196a0c2a5680b15ebce3e654da37129a9fe40ebf51b", [:mix], [], "hexpm", "1b9754f15e3940a143baafd19da12293f100044df69ea12db5d72878312ae6ab"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"tesla": {:hex, :tesla, "1.3.3", "26ae98627af5c406584aa6755ab5fc96315d70d69a24dd7f8369cfcb75094a45", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2648f1c276102f9250299e0b7b57f3071c67827349d9173f34c281756a1b124c"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"tesla": {:hex, :tesla, "1.5.0", "7ee3616be87024a2b7231ae14474310c9b999c3abb1f4f8dbc70f86bd9678eef", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "1d0385e41fbd76af3961809088aef15dec4c2fdaab97b1c93c6484cb3695a122"},
"timex": {:hex, :timex, "3.6.3", "58ce6c9eda8ed47fc80c24dde09d481465838d3bcfc230949287fc1b0b0041c1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "6d69f4f95fcf5684102a9cb3cf92c5ba6545bd60ed8d8a6a93cd2a4a4fb0d9ec"},
"timex_ecto": {:hex, :timex_ecto, "3.4.0", "7871043345626a591bfa3e313aa271df4a4eda79f51eb69e83f326f0b8f3181c", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:timex, "~> 3.6", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "4237fa971c8fb9baeeb901b3ae4efee663e0da335e291228f215d99cf8a64799"},
"tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"wallaby": {:hex, :wallaby, "0.28.0", "2ff217c0f245cadb3e5d91748ebcf0102873ceb9ef8a3507717c8bdd73915668", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.1.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "e58112650d0b51e81714a626eab7d486d7a77342c9bbc2ba262b6653f9b22558"},
"web_driver_client": {:hex, :web_driver_client, "0.1.0", "19466a989c76b7ec803c796cec0fec4611a64f445fd5120ce50c9e3817e09c2c", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "c9c031ca915e8fc75b5e24ac93503244f3cc406dd7f53047087a45aa62d60e9e"},
"wallaby": {:hex, :wallaby, "0.30.1", "81342a34080867ab359aca23de4d1d8c6bbdeb35d8ce2a8c42e42b758d539963", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "457251df6a94ff80816524136edbce6400cb1ee979586c90224ff634e9543d78"},
"web_driver_client": {:hex, :web_driver_client, "0.2.0", "63b76cd9eb3b0716ec5467a0f8bead73d3d9612e63f7560d21357f03ad86e31a", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "83cc6092bc3e74926d1c8455f0ce927d5d1d36707b74d9a65e38c084aab0350f"},
}

View File

@ -1 +1,3 @@
node_version=10.16.0
node_version=14.15.4
yarn_version=1.22.10

View File

@ -76,15 +76,13 @@ defmodule Chess.AuthTest do
test "authenticate_user/1 returns false on incorrect password " do
user_fixture(email: "link@hyrule.com", password: "eyeofsheikah")
assert {:error, message} =
Auth.authenticate_user("link@hyrule.com", "shadowtemple")
assert {:error, message} = Auth.authenticate_user("link@hyrule.com", "shadowtemple")
assert message == "invalid password"
end
test "authenticate_user/1 returns true on correct password " do
user = user_fixture(email: "link@hyrule.com", password: "eyeofsheikah")
assert {:ok, ^user} =
Auth.authenticate_user("link@hyrule.com", "eyeofsheikah")
assert {:ok, ^user} = Auth.authenticate_user("link@hyrule.com", "eyeofsheikah")
end
end
end

View File

@ -6,35 +6,62 @@ defmodule Chess.BoardTest do
alias Chess.Board
test "returns a piece from the board" do
board = Board.default
board = Board.default()
expected_piece = %{"type" => "pawn", "colour" => "white"}
assert Board.piece(board, {4, 1}) == expected_piece
end
test "finds pieces on the board" do
board = Board.default
board = Board.default()
piece = %{"type" => "pawn", "colour" => "white"}
expected_result = [
{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
{0, 1},
{1, 1},
{2, 1},
{3, 1},
{4, 1},
{5, 1},
{6, 1},
{7, 1}
]
assert Board.search(board, piece) == expected_result
end
test "finds pieces on the board with a partial search" do
board = Board.default
board = Board.default()
piece = %{"colour" => "white"}
expected_result = [
{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},
] |> Enum.sort
expected_result =
[
{0, 1},
{1, 1},
{2, 1},
{3, 1},
{4, 1},
{5, 1},
{6, 1},
{7, 1},
{0, 0},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{6, 0},
{7, 0}
]
|> Enum.sort()
assert Board.search(board, piece) == expected_result
end
test "finds a single piece on the board" do
board = Board.default
board = Board.default()
piece = %{"type" => "king", "colour" => "black"}
assert Board.search(board, piece) == [{4, 7}]
@ -42,44 +69,41 @@ defmodule Chess.BoardTest do
test "moves a piece" do
board = %{
"3,0" => %{"type" => "queen", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"}
}
%{board: new_board} =
Board.move_piece(board, %{"from" => [3, 0], "to" => [5, 2]})
%{board: new_board} = Board.move_piece(board, %{"from" => [3, 0], "to" => [5, 2]})
assert new_board == %{
"5,2" => %{"type" => "queen", "colour" => "white"},
}
"5,2" => %{"type" => "queen", "colour" => "white"}
}
end
test "can perform a castling move on the kings side" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}
}
%{board: new_board} =
Board.move_piece(board, %{"from" => [4, 0], "to" => [6, 0]})
%{board: new_board} = Board.move_piece(board, %{"from" => [4, 0], "to" => [6, 0]})
assert new_board == %{
"6,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "rook", "colour" => "white"},
}
"6,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "rook", "colour" => "white"}
}
end
test "can perform a castling move on the queens side" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
%{board: new_board} =
Board.move_piece(board, %{"from" => [4, 0], "to" => [2, 0]})
%{board: new_board} = Board.move_piece(board, %{"from" => [4, 0], "to" => [2, 0]})
assert new_board == %{
"2,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "rook", "colour" => "white"},
}
"2,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "rook", "colour" => "white"}
}
end
end

View File

@ -8,7 +8,7 @@ defmodule Chess.GameStateTest do
test "king is in check" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"4,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{"type" => "queen", "colour" => "black"}
}
assert GameState.king_in_check?(board, "white")
@ -17,7 +17,7 @@ defmodule Chess.GameStateTest do
test "king is not in check" do
board = %{
"5,0" => %{"type" => "king", "colour" => "white"},
"4,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{"type" => "queen", "colour" => "black"}
}
refute GameState.king_in_check?(board, "white")
@ -26,7 +26,7 @@ defmodule Chess.GameStateTest do
test "king is in check by a knight" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"3,2" => %{"type" => "knight", "colour" => "black"},
"3,2" => %{"type" => "knight", "colour" => "black"}
}
assert GameState.king_in_check?(board, "white")
@ -35,7 +35,7 @@ defmodule Chess.GameStateTest do
test "king is in check by a pawn" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"3,1" => %{"type" => "pawn", "colour" => "black"},
"3,1" => %{"type" => "pawn", "colour" => "black"}
}
assert GameState.king_in_check?(board, "white")
@ -45,7 +45,7 @@ defmodule Chess.GameStateTest do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"0,4" => %{"type" => "queen", "colour" => "black"},
"1,4" => %{"type" => "rook", "colour" => "black"},
"1,4" => %{"type" => "rook", "colour" => "black"}
}
assert GameState.player_checkmated?(board, "white")
@ -54,7 +54,7 @@ defmodule Chess.GameStateTest do
test "king is not in checkmate by a queen" do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"0,4" => %{"type" => "queen", "colour" => "black"},
"0,4" => %{"type" => "queen", "colour" => "black"}
}
refute GameState.player_checkmated?(board, "white")
@ -64,7 +64,7 @@ defmodule Chess.GameStateTest do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"1,1" => %{"type" => "queen", "colour" => "black"},
"2,3" => %{"type" => "knight", "colour" => "black"},
"2,3" => %{"type" => "knight", "colour" => "black"}
}
assert GameState.player_checkmated?(board, "white")
@ -75,7 +75,7 @@ defmodule Chess.GameStateTest do
"0,0" => %{"type" => "king", "colour" => "white"},
"2,0" => %{"type" => "knight", "colour" => "white"},
"0,5" => %{"type" => "queen", "colour" => "black"},
"1,5" => %{"type" => "rook", "colour" => "black"},
"1,5" => %{"type" => "rook", "colour" => "black"}
}
refute GameState.player_checkmated?(board, "white")
@ -86,7 +86,7 @@ defmodule Chess.GameStateTest do
"0,0" => %{"type" => "king", "colour" => "white"},
"2,3" => %{"type" => "bishop", "colour" => "white"},
"0,5" => %{"type" => "queen", "colour" => "black"},
"1,5" => %{"type" => "rook", "colour" => "black"},
"1,5" => %{"type" => "rook", "colour" => "black"}
}
refute GameState.player_checkmated?(board, "white")
@ -96,7 +96,7 @@ defmodule Chess.GameStateTest do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"1,2" => %{"type" => "rook", "colour" => "black"},
"2,1" => %{"type" => "rook", "colour" => "black"},
"2,1" => %{"type" => "rook", "colour" => "black"}
}
assert GameState.player_stalemated?(board, "white")

View File

@ -12,18 +12,18 @@ defmodule Chess.MoveListTest do
%Move{
piece: %{"type" => "pawn", "colour" => "white"},
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
to: %{"file" => 4, "rank" => 3}
},
%Move{
piece: %{"type" => "pawn", "colour" => "black"},
from: %{"file" => 4, "rank" => 6},
to: %{"file" => 4, "rank" => 4},
to: %{"file" => 4, "rank" => 4}
},
%Move{
piece: %{"type" => "knight", "colour" => "white"},
from: %{"file" => 1, "rank" => 0},
to: %{"file" => 2, "rank" => 2},
},
to: %{"file" => 2, "rank" => 2}
}
]
expected_result = [
@ -51,7 +51,7 @@ defmodule Chess.MoveListTest do
from: "b1",
to: "c3"
}
],
]
]
assert MoveList.transform(moves) == expected_result

View File

@ -6,7 +6,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not being attacked" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"2,1" => %{"type" => "rook", "colour" => "black"},
"2,1" => %{"type" => "rook", "colour" => "black"}
}
refute Piece.attacked?(board, {4, 5})
@ -15,7 +15,7 @@ defmodule Chess.Moves.PieceTest do
test "piece on the edge of the board is not being attacked" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"2,7" => %{"type" => "rook", "colour" => "black"},
"2,7" => %{"type" => "rook", "colour" => "black"}
}
refute Piece.attacked?(board, {4, 0})
@ -25,7 +25,7 @@ defmodule Chess.Moves.PieceTest do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"4,1" => %{"type" => "pawn", "colour" => "white"},
"7,3" => %{"type" => "bishop", "colour" => "black"},
"7,3" => %{"type" => "bishop", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 0})
@ -34,7 +34,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not being attacked by piece of its own colour" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"2,5" => %{"type" => "rook", "colour" => "white"},
"2,5" => %{"type" => "rook", "colour" => "white"}
}
refute Piece.attacked?(board, {4, 5})
@ -43,7 +43,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a rook" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"2,5" => %{"type" => "rook", "colour" => "black"},
"2,5" => %{"type" => "rook", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -52,7 +52,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a bishop" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"6,7" => %{"type" => "bishop", "colour" => "black"},
"6,7" => %{"type" => "bishop", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -61,7 +61,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a queen" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"6,7" => %{"type" => "queen", "colour" => "black"},
"6,7" => %{"type" => "queen", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -70,7 +70,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not attacked by a knight" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"7,7" => %{"type" => "knight", "colour" => "black"},
"7,7" => %{"type" => "knight", "colour" => "black"}
}
refute Piece.attacked?(board, {4, 5})
@ -79,7 +79,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a knight" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"5,7" => %{"type" => "knight", "colour" => "black"},
"5,7" => %{"type" => "knight", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -88,7 +88,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a pawn" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"5,6" => %{"type" => "pawn", "colour" => "black"},
"5,6" => %{"type" => "pawn", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -97,7 +97,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not attacked by a pawn directly in front" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"4,6" => %{"type" => "pawn", "colour" => "black"},
"4,6" => %{"type" => "pawn", "colour" => "black"}
}
refute Piece.attacked?(board, {4, 5})

View File

@ -7,10 +7,21 @@ defmodule Chess.Moves.Pieces.BishopTest do
board = %{"4,5" => %{"type" => "bishop", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{0, 1}, {1, 2}, {2, 3}, {3, 4}, {5, 6}, {6, 7},
{2, 7}, {3, 6}, {5, 4}, {6, 3}, {7, 2},
])
expected_moves =
Enum.sort([
{0, 1},
{1, 2},
{2, 3},
{3, 4},
{5, 6},
{6, 7},
{2, 7},
{3, 6},
{5, 4},
{6, 3},
{7, 2}
])
assert Enum.sort(moves) == expected_moves
end
@ -18,39 +29,60 @@ defmodule Chess.Moves.Pieces.BishopTest do
board = %{"0,0" => %{"type" => "bishop", "colour" => "white"}}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
])
expected_moves =
Enum.sort([
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5},
{6, 6},
{7, 7}
])
assert Enum.sort(moves) == expected_moves
end
test "bishops are blocked by pieces of their own colour" do
board = %{
"0,0" => %{"type" => "bishop", "colour" => "white"},
"5,5" => %{"type" => "king", "colour" => "white"},
"5,5" => %{"type" => "king", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{1, 1}, {2, 2}, {3, 3}, {4, 4},
])
expected_moves =
Enum.sort([
{1, 1},
{2, 2},
{3, 3},
{4, 4}
])
assert Enum.sort(moves) == expected_moves
end
test "bishops can take an opponents piece" do
board = %{
"0,0" => %{"type" => "bishop", "colour" => "white"},
"5,5" => %{"type" => "knight", "colour" => "black"},
"5,5" => %{"type" => "knight", "colour" => "black"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5},
])
expected_moves =
Enum.sort([
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,28 +7,42 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
test "king can move two spaces to castle with the king side rook" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
{6, 0},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1},
{6, 0}
])
assert Enum.sort(moves) == expected_moves
end
test "king can move two spaces to castle with the queen side rook" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
{2, 0},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1},
{2, 0}
])
assert Enum.sort(moves) == expected_moves
end
@ -36,13 +50,19 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
@ -50,13 +70,20 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"2,7" => %{"type" => "queen", "colour" => "black"},
"2,7" => %{"type" => "queen", "colour" => "black"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
@ -64,88 +91,119 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"3,7" => %{"type" => "queen", "colour" => "black"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the king has moved" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
move_list = [
%Move{
from: %{"file" => 4, "rank" => 0},
to: %{"file" => 4, "rank" => 1},
piece: %{"type" => "king", "colour" => "white"}
piece: %{"type" => "king", "colour" => "white"}
},
%Move{
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 0},
piece: %{"type" => "king", "colour" => "white"}
},
piece: %{"type" => "king", "colour" => "white"}
}
]
moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the queen side rook has moved" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
move_list = [
%Move{
from: %{"file" => 0, "rank" => 0},
to: %{"file" => 0, "rank" => 1},
piece: %{"type" => "rook", "colour" => "white"}
piece: %{"type" => "rook", "colour" => "white"}
},
%Move{
from: %{"file" => 0, "rank" => 1},
to: %{"file" => 0, "rank" => 0},
piece: %{"type" => "rook", "colour" => "white"}
},
piece: %{"type" => "rook", "colour" => "white"}
}
]
moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the king side rook has moved" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}
}
move_list = [
%Move{
from: %{"file" => 7, "rank" => 0},
to: %{"file" => 7, "rank" => 1},
piece: %{"type" => "rook", "colour" => "white"}
piece: %{"type" => "rook", "colour" => "white"}
},
%Move{
from: %{"file" => 7, "rank" => 1},
to: %{"file" => 7, "rank" => 0},
piece: %{"type" => "rook", "colour" => "white"}
},
piece: %{"type" => "rook", "colour" => "white"}
}
]
moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
end

View File

@ -7,9 +7,18 @@ defmodule Chess.Moves.Pieces.KingTest do
board = %{"4,5" => %{"type" => "king", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{3, 4}, {4, 4}, {5, 4}, {5, 5}, {5, 6}, {4, 6}, {3, 6}, {3, 5},
])
expected_moves =
Enum.sort([
{3, 4},
{4, 4},
{5, 4},
{5, 5},
{5, 6},
{4, 6},
{3, 6},
{3, 5}
])
assert Enum.sort(moves) == expected_moves
end
@ -17,26 +26,34 @@ defmodule Chess.Moves.Pieces.KingTest do
board = %{"0,0" => %{"type" => "king", "colour" => "white"}}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {1, 1}, {1, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{1, 1},
{1, 0}
])
assert Enum.sort(moves) == expected_moves
end
test "kings are blocked by pieces of the same colour" do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"1,1" => %{"type" => "rook", "colour" => "white"},
"1,1" => %{"type" => "rook", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {1, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{1, 0}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,9 +7,18 @@ defmodule Chess.Moves.Pieces.KnightTest do
board = %{"4,5" => %{"type" => "knight", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{3, 7}, {5, 7}, {6, 6}, {6, 4}, {5, 3}, {3, 3}, {2, 4}, {2, 6},
])
expected_moves =
Enum.sort([
{3, 7},
{5, 7},
{6, 6},
{6, 4},
{5, 3},
{3, 3},
{2, 4},
{2, 6}
])
assert Enum.sort(moves) == expected_moves
end
@ -17,26 +26,32 @@ defmodule Chess.Moves.Pieces.KnightTest do
board = %{"0,0" => %{"type" => "knight", "colour" => "white"}}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{1, 2}, {2, 1}
])
expected_moves =
Enum.sort([
{1, 2},
{2, 1}
])
assert Enum.sort(moves) == expected_moves
end
test "knights are blocked by other pieces of the same colour" do
board = %{
"0,0" => %{"type" => "knight", "colour" => "white"},
"1,2" => %{"type" => "king", "colour" => "white"},
"1,2" => %{"type" => "king", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{2, 1},
])
expected_moves =
Enum.sort([
{2, 1}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -36,8 +36,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "pawn is blocked from moving two squares by another piece" do
board = %{
"4,1" => %{"type" => "pawn", "colour" => "white"},
"4,3" => %{"type" => "pawn", "colour" => "black"},
"4,3" => %{"type" => "pawn", "colour" => "black"}
}
moves = Moves.available(board, {4, 1})
expected_moves = [{4, 2}]
@ -47,8 +48,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "pawn is blocked from moving one or two squares by another piece" do
board = %{
"4,1" => %{"type" => "pawn", "colour" => "white"},
"4,2" => %{"type" => "pawn", "colour" => "black"},
"4,2" => %{"type" => "pawn", "colour" => "black"}
}
moves = Moves.available(board, {4, 1})
expected_moves = []
@ -58,8 +60,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "pawn is blocked from moving one square by another piece" do
board = %{
"4,2" => %{"type" => "pawn", "colour" => "white"},
"4,3" => %{"type" => "pawn", "colour" => "black"},
"4,3" => %{"type" => "pawn", "colour" => "black"}
}
moves = Moves.available(board, {4, 2})
expected_moves = []
@ -69,8 +72,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "white pawn can take an opponents piece" do
board = %{
"4,2" => %{"type" => "pawn", "colour" => "white"},
"5,3" => %{"type" => "pawn", "colour" => "black"},
"5,3" => %{"type" => "pawn", "colour" => "black"}
}
moves = Moves.available(board, {4, 2})
expected_moves = [{4, 3}, {5, 3}]
@ -80,8 +84,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "black pawn can take an opponents piece" do
board = %{
"6,6" => %{"type" => "pawn", "colour" => "black"},
"5,5" => %{"type" => "pawn", "colour" => "white"},
"5,5" => %{"type" => "pawn", "colour" => "white"}
}
moves = Moves.available(board, {6, 6})
expected_moves = [{6, 5}, {6, 4}, {5, 5}]
@ -89,6 +94,6 @@ defmodule Chess.Moves.Pieces.PawnTest do
end
def default_board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,12 +7,35 @@ defmodule Chess.Moves.Pieces.QueenTest do
board = %{"4,5" => %{"type" => "queen", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}, {4, 6}, {4, 7},
{0, 5}, {1, 5}, {2, 5}, {3, 5}, {5, 5}, {6, 5}, {7, 5},
{0, 1}, {1, 2}, {2, 3}, {3, 4}, {5, 6}, {6, 7},
{2, 7}, {3, 6}, {5, 4}, {6, 3}, {7, 2},
])
expected_moves =
Enum.sort([
{4, 0},
{4, 1},
{4, 2},
{4, 3},
{4, 4},
{4, 6},
{4, 7},
{0, 5},
{1, 5},
{2, 5},
{3, 5},
{5, 5},
{6, 5},
{7, 5},
{0, 1},
{1, 2},
{2, 3},
{3, 4},
{5, 6},
{6, 7},
{2, 7},
{3, 6},
{5, 4},
{6, 3},
{7, 2}
])
assert Enum.sort(moves) == expected_moves
end
@ -20,15 +43,30 @@ defmodule Chess.Moves.Pieces.QueenTest do
board = %{
"0,0" => %{"type" => "queen", "colour" => "white"},
"0,5" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "bishop", "colour" => "white"},
"5,0" => %{"type" => "bishop", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4},
{1, 0}, {2, 0}, {3, 0}, {4, 0},
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5},
{6, 6},
{7, 7}
])
assert Enum.sort(moves) == expected_moves
end
@ -36,19 +74,36 @@ defmodule Chess.Moves.Pieces.QueenTest do
board = %{
"0,0" => %{"type" => "queen", "colour" => "white"},
"0,5" => %{"type" => "knight", "colour" => "black"},
"5,0" => %{"type" => "rook", "colour" => "black"},
"5,0" => %{"type" => "rook", "colour" => "black"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5},
{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0},
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{0, 5},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5},
{6, 6},
{7, 7}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,10 +7,24 @@ defmodule Chess.Moves.Pieces.RookTest do
board = %{"4,5" => %{"type" => "rook", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}, {4, 6}, {4, 7},
{0, 5}, {1, 5}, {2, 5}, {3, 5}, {5, 5}, {6, 5}, {7, 5},
])
expected_moves =
Enum.sort([
{4, 0},
{4, 1},
{4, 2},
{4, 3},
{4, 4},
{4, 6},
{4, 7},
{0, 5},
{1, 5},
{2, 5},
{3, 5},
{5, 5},
{6, 5},
{7, 5}
])
assert Enum.sort(moves) == expected_moves
end
@ -18,42 +32,81 @@ defmodule Chess.Moves.Pieces.RookTest do
board = %{"0,0" => %{"type" => "rook", "colour" => "white"}}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7},
{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{0, 5},
{0, 6},
{0, 7},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{6, 0},
{7, 0}
])
assert Enum.sort(moves) == expected_moves
end
test "rooks are blocked by other pieces of the same colour" do
board = %{
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,5" => %{"type" => "king", "colour" => "white"},
"0,5" => %{"type" => "king", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4},
{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{6, 0},
{7, 0}
])
assert Enum.sort(moves) == expected_moves
end
test "rooks can take an opponents piece" do
board = %{
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,5" => %{"type" => "knight", "colour" => "black"},
"0,5" => %{"type" => "knight", "colour" => "black"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5},
{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{0, 5},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{6, 0},
{7, 0}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,51 +7,59 @@ defmodule Chess.Repo.QueriesTest do
describe "opponents" do
test "it finds a user on a partial name match" do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent = insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent =
insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
result =
user
|> Queries.opponents("zelda")
|> Repo.one
|> Repo.one()
assert result.id == opponent.id
end
test "it finds a user on a complete email match" do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent = insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent =
insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
result =
user
|> Queries.opponents("zelda@hyrule.com")
|> Repo.one
|> Repo.one()
assert result.id == opponent.id
end
test "it does not find a user on a partial email" do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
@ -61,7 +69,7 @@ defmodule Chess.Repo.QueriesTest do
result =
user
|> Queries.opponents("hyrule")
|> Repo.one
|> Repo.one()
assert result == nil
end

View File

@ -20,6 +20,7 @@ defmodule Chess.Store.GameTest do
opponent_id: opponent.id,
turn: "white"
}
changeset = Game.changeset(%Game{}, attrs)
assert changeset.valid?
@ -33,6 +34,7 @@ defmodule Chess.Store.GameTest do
opponent_id: 2,
turn: "white"
}
changeset = Game.changeset(%Game{}, attrs)
assert changeset.valid?
@ -81,18 +83,20 @@ defmodule Chess.Store.GameTest do
user = insert(:user)
opponent = insert(:opponent)
game = insert(:game, %{
board: Board.default,
user_id: user.id,
opponent_id: opponent.id,
})
game =
insert(:game, %{
board: Board.default(),
user_id: user.id,
opponent_id: opponent.id
})
move_params = %{"from" => [4, 1], "to" => [4, 3]}
move_params = %{from: {4, 1}, to: {4, 3}}
changeset = Game.move_changeset(
game,
Board.move_piece(game.board, move_params)
)
changeset =
Game.move_changeset(
game,
Board.move_piece(game.board, move_params)
)
assert {:ok, new_game} = Repo.update(changeset)
assert new_game.turn == "black"

View File

@ -14,29 +14,32 @@ defmodule Chess.Store.MoveTest do
user = insert(:user)
opponent = insert(:opponent)
game = insert(:game, %{
board: Board.default,
user_id: user.id,
opponent_id: opponent.id,
})
game =
insert(:game, %{
board: Board.default(),
user_id: user.id,
opponent_id: opponent.id
})
changeset = Move.changeset(%Move{}, %{
game_id: game.id,
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
piece: %{"type" => "pawn", "colour" => "white"},
})
changeset =
Move.changeset(%Move{}, %{
game_id: game.id,
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
piece: %{"type" => "pawn", "colour" => "white"}
})
assert changeset.valid?
assert {:ok, _move} = Repo.insert(changeset)
end
test "move is invalid without a game" do
changeset = Move.changeset(%Move{}, %{
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
piece: %{"type" => "pawn", "colour" => "white"},
})
changeset =
Move.changeset(%Move{}, %{
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
piece: %{"type" => "pawn", "colour" => "white"}
})
refute changeset.valid?
end
@ -45,16 +48,18 @@ defmodule Chess.Store.MoveTest do
user = insert(:user)
opponent = insert(:opponent)
game = insert(:game, %{
board: Board.default,
user_id: user.id,
opponent_id: opponent.id,
})
game =
insert(:game, %{
board: Board.default(),
user_id: user.id,
opponent_id: opponent.id
})
changeset = Move.changeset(%Move{}, %{
game_id: game.id,
piece: %{"type" => "pawn", "colour" => "white"},
})
changeset =
Move.changeset(%Move{}, %{
game_id: game.id,
piece: %{"type" => "pawn", "colour" => "white"}
})
refute changeset.valid?
end
@ -63,17 +68,19 @@ defmodule Chess.Store.MoveTest do
user = insert(:user)
opponent = insert(:opponent)
game = insert(:game, %{
board: Board.default,
user_id: user.id,
opponent_id: opponent.id,
})
game =
insert(:game, %{
board: Board.default(),
user_id: user.id,
opponent_id: opponent.id
})
changeset = Move.changeset(%Move{}, %{
game_id: game.id,
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
})
changeset =
Move.changeset(%Move{}, %{
game_id: game.id,
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3}
})
refute changeset.valid?
end
@ -83,16 +90,16 @@ defmodule Chess.Store.MoveTest do
piece: %{"type" => "pawn", "colour" => "white"},
piece_captured: %{"type" => "pawn", "colour" => "black"},
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
to: %{"file" => 4, "rank" => 3}
}
assert Move.transform(move) == %{
id: nil,
piece: %{"type" => "pawn", "colour" => "white"},
piece_captured: %{"type" => "pawn", "colour" => "black"},
from: "e2",
to: "e4"
}
id: nil,
piece: %{"type" => "pawn", "colour" => "white"},
piece_captured: %{"type" => "pawn", "colour" => "black"},
from: "e2",
to: "e4"
}
end
end
end

View File

@ -8,10 +8,12 @@ defmodule ChessWeb.GameChannelTest do
test "assigns game_id to the socket after join" do
user = insert(:user)
game = insert(:game, %{
user_id: user.id,
opponent_id: insert(:opponent).id
})
game =
insert(:game, %{
user_id: user.id,
opponent_id: insert(:opponent).id
})
token = Phoenix.Token.sign(@endpoint, "game socket", user.id)
{:ok, socket} = connect(UserSocket, %{"token" => token})
@ -24,20 +26,22 @@ defmodule ChessWeb.GameChannelTest do
test "returns the game state after join" do
user = insert(:user)
opponent = insert(:opponent, %{name: "Daruk"})
game = insert(:game, %{
user_id: user.id,
opponent_id: opponent.id
})
game =
insert(:game, %{
user_id: user.id,
opponent_id: opponent.id
})
token = Phoenix.Token.sign(@endpoint, "game socket", user.id)
{:ok, socket} = connect(UserSocket, %{"token" => token})
{:ok, _, _} = subscribe_and_join(socket, "game:#{game.id}", %{})
assert_push "game:update", %{
assert_push("game:update", %{
player: "white",
opponent: "Daruk",
turn: "white",
}
turn: "white"
})
end
end

View File

@ -60,7 +60,7 @@ defmodule ChessWeb.GameControllerTest do
|> login(user)
|> get(game_path(conn, :show, game))
assert html_response(conn, 200) =~ "<div id=\"game\" data-game-id=\"#{game.id}\">"
assert html_response(conn, 200) =~ "<div data-game-id=\"#{game.id}\">"
end
test "does not show a game if the user is not a player", %{conn: conn} do
@ -74,25 +74,25 @@ defmodule ChessWeb.GameControllerTest do
conn
|> login(other_user)
assert_error_sent 404, fn ->
get conn, game_path(conn, :show, game.id)
end
assert_error_sent(404, fn ->
get(conn, game_path(conn, :show, game.id))
end)
end
test "renders page not found when id is nonexistent", %{conn: conn} do
user = insert(:user)
conn = login(conn, user)
assert_error_sent 404, fn ->
get conn, game_path(conn, :show, -1)
end
assert_error_sent(404, fn ->
get(conn, game_path(conn, :show, -1))
end)
end
test "deletes game", %{conn: conn} do
game = Repo.insert! %Game{}
game = Repo.insert!(%Game{})
user = insert(:user)
conn = login(conn, user)
conn = delete conn, game_path(conn, :delete, game)
conn = delete(conn, game_path(conn, :delete, game))
assert redirected_to(conn) == game_path(conn, :index)
refute Repo.get(Game, game.id)
end

View File

@ -2,7 +2,7 @@ defmodule ChessWeb.SessionControllerTest do
use ChessWeb.ConnCase
test "shows log in form", %{conn: conn} do
conn = get conn, session_path(conn, :new)
conn = get(conn, session_path(conn, :new))
assert html_response(conn, 200) =~ "Log in"
end
end

View File

@ -40,22 +40,24 @@ defmodule Chess.Features.GamesTest do
|> click(button("Create game"))
session
|> assert_has(
css(".help-block", text: "can't be blank")
)
|> assert_has(css(".help-block", text: "can't be blank"))
end
test "can only see own games", %{session: session} do
opponent = insert(:user, %{
name: "Urbosa",
email: "urbosa@gerudo.town",
password: "gerudoqueen"
})
user = insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
opponent =
insert(:user, %{
name: "Urbosa",
email: "urbosa@gerudo.town",
password: "gerudoqueen"
})
user =
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
insert(:game, %{user_id: user.id, opponent_id: opponent.id})
session
@ -72,16 +74,20 @@ defmodule Chess.Features.GamesTest do
end
test "can see games as an opponent", %{session: session} do
opponent = insert(:user, %{
name: "Urbosa",
email: "urbosa@gerudo.town",
password: "gerudoqueen"
})
user = insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
opponent =
insert(:user, %{
name: "Urbosa",
email: "urbosa@gerudo.town",
password: "gerudoqueen"
})
user =
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
insert(:game, %{user_id: user.id, opponent_id: opponent.id})
session

Some files were not shown because too many files have changed in this diff Show More