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/HashNuke/heroku-buildpack-elixir
https://github.com/gjaldon/heroku-buildpack-phoenix-static 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 elixir 1.14.1
python 2.7.14 python 3.9.1
nodejs 10.16.0 nodejs 16.14.0
erlang 23.2.3 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%; width: 1.5%;
} }
.board__container {
grid-area: board;
}
.board { .board {
background: $background-color; background: $background-color;
border-collapse: unset; border-collapse: unset;
border-radius: 2.8%; border-radius: 2.8%;
border-spacing: 1px; border-spacing: 1px;
color: $foreground-color; color: $foreground-color;
grid-area: board;
height: var(--board-size); height: var(--board-size);
padding: calc(var(--board-size) / 20); padding: calc(var(--board-size) / 20);
position: relative; position: relative;
@ -58,6 +61,14 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
.board__body {
flex-direction: column-reverse;
}
.board__row {
flex-direction: row;
}
} }
.board--player-is-black { .board--player-is-black {
@ -70,20 +81,45 @@
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;
} }
.board__body {
flex-direction: column;
}
.board__row {
flex-direction: row-reverse;
}
} }
.board__body { .board__body {
background: $background-color; background: $background-color;
border: 0.25rem solid $foreground-color; border: 0.25rem solid $foreground-color;
border-radius: calc(var(--board-size) / 100); border-radius: calc(var(--board-size) / 100);
display: grid; display: flex;
grid-template-columns: repeat(8, 1fr); flex-direction: column;
grid-template-rows: repeat(8, 1fr);
height: 100%; height: 100%;
padding: 1%; padding: 1%;
width: 100%; 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 { .board__rank-labels {
display: none; display: none;
height: 90%; height: 90%;

View File

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

View File

@ -57,36 +57,13 @@
border-radius: 4%; border-radius: 4%;
margin: 0.5px; margin: 0.5px;
position: relative; position: relative;
flex-grow: 1;
// This is to ensure the squares can be clicked on in PhantomJS // This is to ensure the squares can be clicked on in PhantomJS
// TODO: Figure out why we need this // TODO: Figure out why we need this
min-height: 20px; min-height: 20px;
min-width: 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 { &::before {
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;

View File

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

View File

@ -1,7 +1,7 @@
$base-border-radius: 0.5rem; $base-border-radius: 0.5rem;
$base-spacing: 2rem; $base-spacing: 2rem;
$small-spacing: $base-spacing / 2; $small-spacing: $base-spacing * 0.5;
$x-small-spacing: $small-spacing / 2; $x-small-spacing: $small-spacing * 0.5;
$base-z-index: 0; $base-z-index: 0;
$container-width: 90%; $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] /// @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 /// @param {number} $num - id of the child
@mixin middle($num) { @mixin middle($num) {
&:nth-child(#{round($num / 2)}) { &:nth-child(#{round($num * 0.5)}) {
@content; @content;
} }
} }

View File

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

View File

@ -12,6 +12,8 @@
/// // Output /// // Output
/// $dimension: 10; /// $dimension: 10;
@use "sass:math";
@function strip-unit($value) { @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" { @if $direction == "up" {
border-color: transparent transparent $color; border-color: transparent transparent $color;
border-width: 0 ($width / 2) $height; border-width: 0 ($width * 0.5) $height;
} @else if $direction == "up-right" { } @else if $direction == "up-right" {
border-color: transparent $color transparent transparent; border-color: transparent $color transparent transparent;
border-width: 0 $width $width 0; border-width: 0 $width $width 0;
} @else if $direction == "right" { } @else if $direction == "right" {
border-color: transparent transparent transparent $color; 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" { } @else if $direction == "down-right" {
border-color: transparent transparent $color; border-color: transparent transparent $color;
border-width: 0 0 $width $width; border-width: 0 0 $width $width;
} @else if $direction == "down" { } @else if $direction == "down" {
border-color: $color transparent transparent; border-color: $color transparent transparent;
border-width: $height ($width / 2) 0; border-width: $height ($width * 0.5) 0;
} @else if $direction == "down-left" { } @else if $direction == "down-left" {
border-color: transparent transparent transparent $color; border-color: transparent transparent transparent $color;
border-width: $width 0 0 $width; border-width: $width 0 0 $width;
} @else if $direction == "left" { } @else if $direction == "left" {
border-color: transparent $color transparent transparent; 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" { } @else if $direction == "up-left" {
border-color: $color transparent transparent; border-color: $color transparent transparent;
border-width: $width $width 0 0; border-width: $width $width 0 0;

View File

@ -19,13 +19,15 @@
/// ///
/// @access private /// @access private
@use "sass:math";
@function _contrast-ratio($color-1, $color-2) { @function _contrast-ratio($color-1, $color-2) {
$-local-lightness-1: _lightness($color-1) + 0.05; $-local-lightness-1: _lightness($color-1) + 0.05;
$-local-lightness-2: _lightness($color-2) + 0.05; $-local-lightness-2: _lightness($color-2) + 0.05;
@if $-local-lightness-1 > $-local-lightness-2 { @if $-local-lightness-1 > $-local-lightness-2 {
@return $-local-lightness-1 / $-local-lightness-2; @return math.div($-local-lightness-1, $-local-lightness-2);
} @else { } @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 /// @access private
@use "sass:math";
@function _gamma($channel) { @function _gamma($channel) {
@if $channel < 0.03928 { @if $channel < 0.03928 {
@return $channel / 12.92; @return math.div($channel, 12.92);
} @else { } @else {
$c: ($channel + 0.055) / 1.055; $c: math.div($channel + 0.055, 1.055);
@return (133 * $c * $c * $c + 155 * $c * $c) / 288; @return math.div(133 * $c * $c * $c + 155 * $c * $c, 288);
} }
} }

View File

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

View File

@ -1,5 +1,20 @@
"use strict"; "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 "@babel/polyfill";
import "phoenix_html"; import "phoenix_html";
@ -7,8 +22,6 @@ import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { createStore } from "redux"; import { createStore } from "redux";
import css from "../css/app.scss";
import Game from "./components/game"; import Game from "./components/game";
import OpponentFinder from "./components/opponent-finder"; import OpponentFinder from "./components/opponent-finder";
import chessBoardReducer from "./reducers/chess-board"; import chessBoardReducer from "./reducers/chess-board";

View File

@ -3,18 +3,19 @@
"description": " ", "description": " ",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"deploy": "webpack --mode production", "deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild"
"watch": "webpack --mode development --watch-stdin"
}, },
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.12.1", "@babel/polyfill": "^7.12.1",
"axios": "^0.21.0", "axios": "^0.21.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"esbuild": "^0.14.21",
"gettext.js": "^1.0.0", "gettext.js": "^1.0.0",
"immutable": "^3.8.2", "immutable": "^3.8.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"phoenix": "file:../deps/phoenix", "phoenix": "^1.7.0-rc.2",
"phoenix_html": "file:../deps/phoenix_html", "phoenix_html": "^3.2.0",
"phoenix_live_view": "^0.18.11",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-redux": "^7.2.4", "react-redux": "^7.2.4",
@ -23,18 +24,7 @@
"url-loader": "^4.1.1" "url-loader": "^4.1.1"
}, },
"devDependencies": { "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", "css-loader": "^5.2.6",
"mini-css-extract-plugin": "^1.6.0", "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"
} }
} }

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" echo "Building assets"
cd assets cd assets
yarn install yarn install
yarn run build yarn run deploy
cd .. cd ..

View File

@ -6,8 +6,8 @@ source /dev/stdin <<< "$(curl -sSL https://raw.githubusercontent.com/codeship/sc
echo "Installing Elixir" echo "Installing Elixir"
source /dev/stdin <<< "$(curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/languages/elixir.sh)" source /dev/stdin <<< "$(curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/languages/elixir.sh)"
echo "Installing PhantomJS" echo "Installing ChromeDriver"
curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/packages/phantomjs.sh | bash -s curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/packages/chromedriver.sh | bash -s
echo "Installing NodeJS" echo "Installing NodeJS"
nvm install $NODE_VERSION 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 # This configuration file is loaded before any dependency and
# is restricted to this project. # is restricted to this project.
use Mix.Config import Config
config :phoenix, :json_library, Jason config :phoenix, :json_library, Jason
@ -16,7 +16,8 @@ config :chess, ChessWeb.Endpoint,
url: [host: "localhost"], url: [host: "localhost"],
secret_key_base: "iiTDTKorCWTFoeBgAkr35XZp22cNIM2RsmnHiHdzKAuSHXUGXx42z7lawAwiu1B1", secret_key_base: "iiTDTKorCWTFoeBgAkr35XZp22cNIM2RsmnHiHdzKAuSHXUGXx42z7lawAwiu1B1",
render_errors: [view: ChessWeb.ErrorView, accepts: ~w(html json)], 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 # Configures Elixir's Logger
config :logger, :console, config :logger, :console,
@ -30,8 +31,27 @@ config :chess, Chess.Auth.Guardian,
secret_key: "vd2vXkrYTTFKSKmNMoS2/Hk4Fxn8BkyzsVArRkxJazdQ3mr6bI4YgAC6f8ODiWlM" secret_key: "vd2vXkrYTTFKSKmNMoS2/Hk4Fxn8BkyzsVArRkxJazdQ3mr6bI4YgAC6f8ODiWlM"
config :formulator, 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 # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # 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 # For development, we disable any cache and enable
# debugging and code reloading. # debugging and code reloading.
@ -10,16 +10,7 @@ config :chess, ChessWeb.Endpoint,
http: [port: 4000], http: [port: 4000],
debug_errors: true, debug_errors: true,
code_reloader: true, code_reloader: true,
check_origin: false, check_origin: false
watchers: [
node: [
"node_modules/webpack/bin/webpack.js",
"--mode",
"development",
"--watch",
cd: Path.expand("../assets", __DIR__)
]
]
# Watch static and templates for browser reloading. # Watch static and templates for browser reloading.
config :chess, ChessWeb.Endpoint, config :chess, ChessWeb.Endpoint,

View File

@ -1,8 +1,8 @@
use Mix.Config import Config
config :chess, ChessWeb.Endpoint, config :chess, ChessWeb.Endpoint,
cache_static_manifest: "priv/static/cache_manifest.json", 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"}], http: [port: {:system, "PORT"}],
root: "./assets", root: "./assets",
secret_key_base: System.get_env("SECRET_KEY_BASE"), 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, # We don't run a server during test. If one is required,
# you can enable the server option below. # you can enable the server option below.
@ -18,7 +18,7 @@ config :chess, Chess.Mailer, adapter: Bamboo.TestAdapter
config :chess, Chess.Repo, config :chess, Chess.Repo,
adapter: Ecto.Adapters.Postgres, adapter: Ecto.Adapters.Postgres,
database: "chess_test", database: "chess_test",
hostname: "localhost", hostname: System.get_env("PGHOST") || "localhost",
port: System.get_env("POSTGRES_PORT") || "5432", port: System.get_env("POSTGRES_PORT") || "5432",
pool: Ecto.Adapters.SQL.Sandbox 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
erlang_version=23.2.3 erlang_version=OTP-24.2.1
# Elixir version # 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]}, {Phoenix.PubSub, [name: Chess.PubSub, adapter: Phoenix.PubSub.PG2]},
{Chess.Repo, []}, {Chess.Repo, []},
{ChessWeb.Endpoint, []}, {ChessWeb.Endpoint, []},
{ChessWeb.Presence, []}, {ChessWeb.Presence, []}
] ]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # 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 import Ecto.Query, warn: false
alias Comeonin.Argon2
alias Chess.Repo alias Chess.Repo
alias Chess.Store.User alias Chess.Store.User
@ -41,11 +40,13 @@ defmodule Chess.Auth do
@doc false @doc false
def authenticate_user(email, password) do def authenticate_user(email, password) do
query = from u in User, query =
where: u.email == ^email from(u in User,
where: u.email == ^email
)
query query
|> Repo.one |> Repo.one()
|> Argon2.check_pass(password) |> Argon2.check_pass(password)
end end
end end

View File

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

View File

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

View File

@ -7,11 +7,11 @@ defmodule Chess.Auth.Pipeline do
module: Chess.Auth.Guardian module: Chess.Auth.Guardian
# If there is a session token, validate it # 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 # 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 # Load the user if either of the verifications worked
plug Guardian.Plug.LoadResource, allow_blank: true plug(Guardian.Plug.LoadResource, allow_blank: true)
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@ defmodule ChessWeb do
def controller do def controller do
quote do quote do
use Phoenix.Controller, namespace: ChessWeb use Phoenix.Controller, namespace: ChessWeb
import Phoenix.LiveView.Controller
alias Chess.Repo alias Chess.Repo
import Ecto import Ecto
@ -31,12 +32,14 @@ defmodule ChessWeb do
def view do def view do
quote do quote do
use Phoenix.View, root: "lib/chess_web/templates", use Phoenix.View,
namespace: ChessWeb root: "lib/chess_web/templates",
namespace: ChessWeb
# Import convenience functions from controllers # Import convenience functions from controllers
import Phoenix.Controller, import Phoenix.Controller,
only: [get_csrf_token: 0, get_flash: 2, view_module: 1] only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
import Phoenix.LiveView.Helpers
# Use all HTML functionality (forms, tags, etc) # Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML use Phoenix.HTML
@ -49,9 +52,45 @@ defmodule ChessWeb do
end end
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 def router do
quote do quote do
use Phoenix.Router use Phoenix.Router
import Phoenix.LiveView.Router
end end
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,57 +4,53 @@ defmodule ChessWeb.Router do
alias Phoenix.Token alias Phoenix.Token
pipeline :browser do pipeline :browser do
plug :accepts, ["html"] plug(:accepts, ["html"])
plug :fetch_session plug(:fetch_session)
plug :fetch_flash plug(:fetch_live_flash)
plug :protect_from_forgery plug(:protect_from_forgery)
plug :put_secure_browser_headers plug(:put_secure_browser_headers)
end end
pipeline :auth do pipeline :auth do
plug Chess.Auth.Pipeline plug(Chess.Auth.Pipeline)
end end
pipeline :ensure_auth do pipeline :ensure_auth do
plug Guardian.Plug.EnsureAuthenticated plug(Guardian.Plug.EnsureAuthenticated)
plug :put_user_token plug(:put_user_token)
end end
pipeline :api do pipeline :api do
plug :fetch_session plug(:fetch_session)
plug :accepts, ["json"] plug(:accepts, ["json"])
end end
scope "/", ChessWeb do scope "/", ChessWeb do
pipe_through [:browser, :auth] # Use the default browser stack # Use the default browser stack
pipe_through([:browser, :auth])
get "/", PageController, :index get("/", PageController, :index)
resources "/session", SessionController, resources("/session", SessionController, only: [:new, :create, :delete], singleton: true)
only: [:new, :create, :delete], singleton: true resources("/registration", RegistrationController, only: [:new, :create], singleton: true)
resources "/registration", RegistrationController,
only: [:new, :create], singleton: true
end end
scope "/", ChessWeb do scope "/", ChessWeb do
pipe_through [:browser, :auth, :ensure_auth] pipe_through([:browser, :auth, :ensure_auth])
resources "/games", GameController, resources("/games", GameController, only: [:index, :new, :create, :show, :delete])
only: [:index, :new, :create, :show, :delete] resources("/profile", ProfileController, only: [:edit, :update], singleton: true)
resources "/profile", ProfileController, resources("/password", PasswordController, only: [:edit, :update], singleton: true)
only: [:edit, :update], singleton: true
resources "/password", PasswordController,
only: [:edit, :update], singleton: true
end end
# Other scopes may use custom stacks. # Other scopes may use custom stacks.
scope "/api", as: :api do 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 end
if Mix.env == :dev do if Mix.env() == :dev do
forward "/sent_emails", Bamboo.SentEmailViewerPlug forward("/sent_emails", Bamboo.SentEmailViewerPlug)
end end
defp put_user_token(conn, _) do 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) %>"> <tr class="<%= turn_class(@conn, game) %>">
<td> <td>
<span class="games-list__player-indicator"> <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> </span>
<%= link gettext( <%= link gettext(
"Game with %{name}", "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> </div>

View File

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

View File

@ -1,7 +1,7 @@
<div class="form"> <div class="form">
<h2><%= gettext "Password" %></h2> <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 %> <%= if @changeset.action do %>
<div class="alert alert-danger"> <div class="alert alert-danger">
<p> <p>

View File

@ -1,7 +1,7 @@
<div class="form"> <div class="form">
<h2><%= gettext "Profile" %></h2> <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 %> <%= if @changeset.action do %>
<div class="alert alert-danger"> <div class="alert alert-danger">
<p> <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 def render("index.json", %{opponents: opponents}) do
%{ %{
opponents: Enum.map(opponents, fn opponent -> opponents:
opponent_attrs(opponent) Enum.map(opponents, fn opponent ->
end) opponent_attrs(opponent)
end)
} }
end end
def opponent_attrs(opponent) do def opponent_attrs(opponent) do
%{ %{
id: opponent.id, id: opponent.id,
name: opponent.name, name: opponent.name
} }
end end
end end

View File

@ -10,7 +10,7 @@ defmodule ChessWeb.ErrorHelpers do
""" """
def error_tag(form, field) do def error_tag(form, field) do
if error = form.errors[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
end end

View File

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

View File

@ -7,8 +7,8 @@ defmodule ChessWeb.GameView do
def won_lost(conn, game) do def won_lost(conn, game) do
if game_over?(game) && game.state == "checkmate" do if game_over?(game) && game.state == "checkmate" do
your_turn?(conn, game) && (your_turn?(conn, game) &&
gettext("You lost") || gettext("You lost")) ||
gettext("You won") gettext("You won")
end end
end end
@ -21,9 +21,12 @@ defmodule ChessWeb.GameView do
cond do cond do
GameState.game_over?(game) -> GameState.game_over?(game) ->
states()[game.state] states()[game.state]
your_turn?(conn, game) -> your_turn?(conn, game) ->
gettext("Your turn") gettext("Your turn")
true -> nil
true ->
nil
end end
end end
@ -34,11 +37,26 @@ defmodule ChessWeb.GameView do
end end
def your_turn?(conn, game) do def your_turn?(conn, game) do
player_colour(conn, game) == game.turn conn
|> current_user()
|> player_colour(game) == game.turn
end end
def player_colour(conn, game) do 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 end
def player(game, user_id) do def player(game, user_id) do
@ -61,7 +79,7 @@ defmodule ChessWeb.GameView do
%{ %{
"checkmate" => gettext("Checkmate!"), "checkmate" => gettext("Checkmate!"),
"stalemate" => gettext("Stalemate"), "stalemate" => gettext("Stalemate"),
"check" => gettext("Check"), "check" => gettext("Check")
} }
end end
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, app: :chess,
version: "0.2.0", version: "0.2.0",
elixir: "~> 1.11.3", elixir: "~> 1.14.1",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(), compilers: [:phoenix] ++ Mix.compilers(),
build_embedded: Mix.env() == :prod, build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
aliases: aliases(), aliases: aliases(),
@ -21,7 +21,7 @@ defmodule Chess.Mixfile do
def application do def application do
[ [
mod: {Chess, []}, mod: {Chess, []},
extra_applications: [:logger] extra_applications: [:logger, :ssl]
] ]
end end
@ -34,25 +34,29 @@ defmodule Chess.Mixfile do
# Type `mix help deps` for examples and options. # Type `mix help deps` for examples and options.
defp deps do defp deps do
[ [
{:argon2_elixir, "~> 1.3"}, {:argon2_elixir, "~> 3.0"},
{:bamboo, "~> 1.0"}, {:bamboo, "~> 2.0"},
{:comeonin, "~> 4.0"}, {:comeonin, "~> 5.0"},
{:cowboy, "~> 2.1"}, {:cowboy, "~> 2.0"},
{:plug_cowboy, "~> 2.1"},
{:credo, "~> 1.0", only: [:dev, :test]}, {:credo, "~> 1.0", only: [:dev, :test]},
{:dart_sass, "~> 0.5", runtime: Mix.env() == :dev},
{:ecto_sql, "~> 3.0"}, {:ecto_sql, "~> 3.0"},
{:formulator, "~> 0.1.6"}, {:floki, "~> 0.34", only: :test},
{:gettext, "~> 0.16.0"}, {:esbuild, "~> 0.6", runtime: Mix.env() == :dev},
{:guardian, "~> 1.0"}, {:formulator, "~> 0.4.0"},
{:gettext, "~> 0.22.0"},
{:guardian, "~> 2.0"},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:phoenix, "~> 1.5.7"}, {:phoenix, "~> 1.6.0"},
{:phoenix_ecto, "~> 4.0"}, {:phoenix_ecto, "~> 4.0"},
{:phoenix_html, "~> 2.0"}, {:phoenix_html, "~> 3.2.0"},
{:phoenix_live_reload, "~> 1.0", only: :dev}, {:phoenix_live_reload, "~> 1.0", only: :dev},
{:phoenix_live_view, "~> 0.18"},
{:phoenix_pubsub, "~> 2.0"}, {:phoenix_pubsub, "~> 2.0"},
{:postgrex, ">= 0.15.0"}, {:plug_cowboy, "~> 2.0"},
{:postgrex, "~> 0.16.0"},
{:secure_random, "~> 0.5"}, {:secure_random, "~> 0.5"},
{:wallaby, "~> 0.28.0", [runtime: false, only: :test]} {:wallaby, "~> 0.30.0", [runtime: false, only: :test]}
] ]
end end
@ -66,7 +70,12 @@ defmodule Chess.Mixfile do
[ [
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"], "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
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"}, "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, "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"}, "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.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, "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"}, "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"}, "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": {: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.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, "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.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"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"}, "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"},
"db_connection": {:hex, :db_connection, "2.3.1", "4c9f3ed1ef37471cbdd2762d6655be11e38193904d9c5c1c9389f1b891a3088e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "abaab61780dde30301d840417890bd9f74131041afd02174cf4e10635b3a63f5"}, "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"}, "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": {: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.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"}, "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.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"}, "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"}, "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"}, "floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"},
"gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm", "dd3a7ea5e3e87ee9df29452dd9560709b4c7cc8141537d0b070155038d92bdf1"}, "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"},
"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"}, "gettext": {:hex, :gettext, "0.22.0", "a25d71ec21b1848957d9207b81fd61cb25161688d282d58bdafef74c2270bdc4", [:mix], [{:expo, "~> 0.3.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "cb0675141576f73720c8e49b4f0fd3f2c69f0cd8c218202724d4aebab8c70ace"},
"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"}, "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"},
"httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"}, "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"}, "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"}, "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.1", "59da64010c69aad6cde2f5b9248b896b84472e99bd18f246085b7b9fe435dcdb", [:mix, :rebar3], [], "hexpm", "078f6c9fb3cd2f4cfafc972c814261a7d1e8d2b3685c0a76eb87e158efff1ac5"}, "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "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"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "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": {: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.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_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, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"}, "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.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_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_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, "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"},
"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"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"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"}, "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"},
"plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, "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"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "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"}, "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.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"secure_random": {:hex, :secure_random, "0.5.1", "c5532b37c89d175c328f5196a0c2a5680b15ebce3e654da37129a9fe40ebf51b", [:mix], [], "hexpm", "1b9754f15e3940a143baafd19da12293f100044df69ea12db5d72878312ae6ab"}, "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"}, "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"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"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"}, "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": {: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"}, "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"}, "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"}, "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"}, "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.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"}, "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 test "authenticate_user/1 returns false on incorrect password " do
user_fixture(email: "link@hyrule.com", password: "eyeofsheikah") user_fixture(email: "link@hyrule.com", password: "eyeofsheikah")
assert {:error, message} = assert {:error, message} = Auth.authenticate_user("link@hyrule.com", "shadowtemple")
Auth.authenticate_user("link@hyrule.com", "shadowtemple")
assert message == "invalid password" assert message == "invalid password"
end end
test "authenticate_user/1 returns true on correct password " do test "authenticate_user/1 returns true on correct password " do
user = user_fixture(email: "link@hyrule.com", password: "eyeofsheikah") user = user_fixture(email: "link@hyrule.com", password: "eyeofsheikah")
assert {:ok, ^user} = assert {:ok, ^user} = Auth.authenticate_user("link@hyrule.com", "eyeofsheikah")
Auth.authenticate_user("link@hyrule.com", "eyeofsheikah")
end end
end end
end end

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not being attacked" do test "piece is not being attacked" do
board = %{ board = %{
"4,5" => %{"type" => "king", "colour" => "white"}, "4,5" => %{"type" => "king", "colour" => "white"},
"2,1" => %{"type" => "rook", "colour" => "black"}, "2,1" => %{"type" => "rook", "colour" => "black"}
} }
refute Piece.attacked?(board, {4, 5}) 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 test "piece on the edge of the board is not being attacked" do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"2,7" => %{"type" => "rook", "colour" => "black"}, "2,7" => %{"type" => "rook", "colour" => "black"}
} }
refute Piece.attacked?(board, {4, 0}) refute Piece.attacked?(board, {4, 0})
@ -25,7 +25,7 @@ defmodule Chess.Moves.PieceTest do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"4,1" => %{"type" => "pawn", "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}) 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 test "piece is not being attacked by piece of its own colour" do
board = %{ board = %{
"4,5" => %{"type" => "king", "colour" => "white"}, "4,5" => %{"type" => "king", "colour" => "white"},
"2,5" => %{"type" => "rook", "colour" => "white"}, "2,5" => %{"type" => "rook", "colour" => "white"}
} }
refute Piece.attacked?(board, {4, 5}) refute Piece.attacked?(board, {4, 5})
@ -43,7 +43,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a rook" do test "piece can be attacked by a rook" do
board = %{ board = %{
"4,5" => %{"type" => "king", "colour" => "white"}, "4,5" => %{"type" => "king", "colour" => "white"},
"2,5" => %{"type" => "rook", "colour" => "black"}, "2,5" => %{"type" => "rook", "colour" => "black"}
} }
assert Piece.attacked?(board, {4, 5}) assert Piece.attacked?(board, {4, 5})
@ -52,7 +52,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a bishop" do test "piece can be attacked by a bishop" do
board = %{ board = %{
"4,5" => %{"type" => "king", "colour" => "white"}, "4,5" => %{"type" => "king", "colour" => "white"},
"6,7" => %{"type" => "bishop", "colour" => "black"}, "6,7" => %{"type" => "bishop", "colour" => "black"}
} }
assert Piece.attacked?(board, {4, 5}) assert Piece.attacked?(board, {4, 5})
@ -61,7 +61,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a queen" do test "piece can be attacked by a queen" do
board = %{ board = %{
"4,5" => %{"type" => "king", "colour" => "white"}, "4,5" => %{"type" => "king", "colour" => "white"},
"6,7" => %{"type" => "queen", "colour" => "black"}, "6,7" => %{"type" => "queen", "colour" => "black"}
} }
assert Piece.attacked?(board, {4, 5}) assert Piece.attacked?(board, {4, 5})
@ -70,7 +70,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not attacked by a knight" do test "piece is not attacked by a knight" do
board = %{ board = %{
"4,5" => %{"type" => "king", "colour" => "white"}, "4,5" => %{"type" => "king", "colour" => "white"},
"7,7" => %{"type" => "knight", "colour" => "black"}, "7,7" => %{"type" => "knight", "colour" => "black"}
} }
refute Piece.attacked?(board, {4, 5}) refute Piece.attacked?(board, {4, 5})
@ -79,7 +79,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a knight" do test "piece can be attacked by a knight" do
board = %{ board = %{
"4,5" => %{"type" => "king", "colour" => "white"}, "4,5" => %{"type" => "king", "colour" => "white"},
"5,7" => %{"type" => "knight", "colour" => "black"}, "5,7" => %{"type" => "knight", "colour" => "black"}
} }
assert Piece.attacked?(board, {4, 5}) assert Piece.attacked?(board, {4, 5})
@ -88,7 +88,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a pawn" do test "piece can be attacked by a pawn" do
board = %{ board = %{
"4,5" => %{"type" => "king", "colour" => "white"}, "4,5" => %{"type" => "king", "colour" => "white"},
"5,6" => %{"type" => "pawn", "colour" => "black"}, "5,6" => %{"type" => "pawn", "colour" => "black"}
} }
assert Piece.attacked?(board, {4, 5}) 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 test "piece is not attacked by a pawn directly in front" do
board = %{ board = %{
"4,5" => %{"type" => "king", "colour" => "white"}, "4,5" => %{"type" => "king", "colour" => "white"},
"4,6" => %{"type" => "pawn", "colour" => "black"}, "4,6" => %{"type" => "pawn", "colour" => "black"}
} }
refute Piece.attacked?(board, {4, 5}) 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"}} board = %{"4,5" => %{"type" => "bishop", "colour" => "white"}}
moves = Moves.available(board, {4, 5}) moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([ expected_moves =
{0, 1}, {1, 2}, {2, 3}, {3, 4}, {5, 6}, {6, 7}, Enum.sort([
{2, 7}, {3, 6}, {5, 4}, {6, 3}, {7, 2}, {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 assert Enum.sort(moves) == expected_moves
end end
@ -18,39 +29,60 @@ defmodule Chess.Moves.Pieces.BishopTest do
board = %{"0,0" => %{"type" => "bishop", "colour" => "white"}} board = %{"0,0" => %{"type" => "bishop", "colour" => "white"}}
moves = Moves.available(board, {0, 0}) moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([ expected_moves =
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7}, Enum.sort([
]) {1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5},
{6, 6},
{7, 7}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
test "bishops are blocked by pieces of their own colour" do test "bishops are blocked by pieces of their own colour" do
board = %{ board = %{
"0,0" => %{"type" => "bishop", "colour" => "white"}, "0,0" => %{"type" => "bishop", "colour" => "white"},
"5,5" => %{"type" => "king", "colour" => "white"}, "5,5" => %{"type" => "king", "colour" => "white"}
} }
moves = Moves.available(board, {0, 0}) moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([ expected_moves =
{1, 1}, {2, 2}, {3, 3}, {4, 4}, Enum.sort([
]) {1, 1},
{2, 2},
{3, 3},
{4, 4}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
test "bishops can take an opponents piece" do test "bishops can take an opponents piece" do
board = %{ board = %{
"0,0" => %{"type" => "bishop", "colour" => "white"}, "0,0" => %{"type" => "bishop", "colour" => "white"},
"5,5" => %{"type" => "knight", "colour" => "black"}, "5,5" => %{"type" => "knight", "colour" => "black"}
} }
moves = Moves.available(board, {0, 0}) moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([ expected_moves =
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, Enum.sort([
]) {1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
def board do def board do
Chess.Board.default Chess.Board.default()
end end
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 test "king can move two spaces to castle with the king side rook" do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}, "7,0" => %{"type" => "rook", "colour" => "white"}
} }
moves = Moves.available(board, {4, 0}) moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([ expected_moves =
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1}, Enum.sort([
{6, 0}, {3, 0},
]) {5, 0},
{3, 1},
{4, 1},
{5, 1},
{6, 0}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
test "king can move two spaces to castle with the queen side rook" do test "king can move two spaces to castle with the queen side rook" do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}, "0,0" => %{"type" => "rook", "colour" => "white"}
} }
moves = Moves.available(board, {4, 0}) moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([ expected_moves =
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1}, Enum.sort([
{2, 0}, {3, 0},
]) {5, 0},
{3, 1},
{4, 1},
{5, 1},
{2, 0}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
@ -36,13 +50,19 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "queen", "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}) moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([ expected_moves =
{5, 0}, {3, 1}, {4, 1}, {5, 1}, Enum.sort([
]) {5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
@ -50,13 +70,20 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "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}) moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([ expected_moves =
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1}, Enum.sort([
]) {3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
@ -64,88 +91,119 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "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}) moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([ expected_moves =
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1}, Enum.sort([
]) {3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
test "cannot castle if the king has moved" do test "cannot castle if the king has moved" do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}, "0,0" => %{"type" => "rook", "colour" => "white"}
} }
move_list = [ move_list = [
%Move{ %Move{
from: %{"file" => 4, "rank" => 0}, from: %{"file" => 4, "rank" => 0},
to: %{"file" => 4, "rank" => 1}, to: %{"file" => 4, "rank" => 1},
piece: %{"type" => "king", "colour" => "white"} piece: %{"type" => "king", "colour" => "white"}
}, },
%Move{ %Move{
from: %{"file" => 4, "rank" => 1}, from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 0}, to: %{"file" => 4, "rank" => 0},
piece: %{"type" => "king", "colour" => "white"} piece: %{"type" => "king", "colour" => "white"}
}, }
] ]
moves = Moves.available(board, {4, 0}, move_list) moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([ expected_moves =
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1}, Enum.sort([
]) {3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
test "cannot castle if the queen side rook has moved" do test "cannot castle if the queen side rook has moved" do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}, "0,0" => %{"type" => "rook", "colour" => "white"}
} }
move_list = [ move_list = [
%Move{ %Move{
from: %{"file" => 0, "rank" => 0}, from: %{"file" => 0, "rank" => 0},
to: %{"file" => 0, "rank" => 1}, to: %{"file" => 0, "rank" => 1},
piece: %{"type" => "rook", "colour" => "white"} piece: %{"type" => "rook", "colour" => "white"}
}, },
%Move{ %Move{
from: %{"file" => 0, "rank" => 1}, from: %{"file" => 0, "rank" => 1},
to: %{"file" => 0, "rank" => 0}, to: %{"file" => 0, "rank" => 0},
piece: %{"type" => "rook", "colour" => "white"} piece: %{"type" => "rook", "colour" => "white"}
}, }
] ]
moves = Moves.available(board, {4, 0}, move_list) moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([ expected_moves =
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1}, Enum.sort([
]) {3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
test "cannot castle if the king side rook has moved" do test "cannot castle if the king side rook has moved" do
board = %{ board = %{
"4,0" => %{"type" => "king", "colour" => "white"}, "4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}, "7,0" => %{"type" => "rook", "colour" => "white"}
} }
move_list = [ move_list = [
%Move{ %Move{
from: %{"file" => 7, "rank" => 0}, from: %{"file" => 7, "rank" => 0},
to: %{"file" => 7, "rank" => 1}, to: %{"file" => 7, "rank" => 1},
piece: %{"type" => "rook", "colour" => "white"} piece: %{"type" => "rook", "colour" => "white"}
}, },
%Move{ %Move{
from: %{"file" => 7, "rank" => 1}, from: %{"file" => 7, "rank" => 1},
to: %{"file" => 7, "rank" => 0}, to: %{"file" => 7, "rank" => 0},
piece: %{"type" => "rook", "colour" => "white"} piece: %{"type" => "rook", "colour" => "white"}
}, }
] ]
moves = Moves.available(board, {4, 0}, move_list) moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([ expected_moves =
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1}, Enum.sort([
]) {3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
end end

View File

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

View File

@ -7,9 +7,18 @@ defmodule Chess.Moves.Pieces.KnightTest do
board = %{"4,5" => %{"type" => "knight", "colour" => "white"}} board = %{"4,5" => %{"type" => "knight", "colour" => "white"}}
moves = Moves.available(board, {4, 5}) moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([ expected_moves =
{3, 7}, {5, 7}, {6, 6}, {6, 4}, {5, 3}, {3, 3}, {2, 4}, {2, 6}, Enum.sort([
]) {3, 7},
{5, 7},
{6, 6},
{6, 4},
{5, 3},
{3, 3},
{2, 4},
{2, 6}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
@ -17,26 +26,32 @@ defmodule Chess.Moves.Pieces.KnightTest do
board = %{"0,0" => %{"type" => "knight", "colour" => "white"}} board = %{"0,0" => %{"type" => "knight", "colour" => "white"}}
moves = Moves.available(board, {0, 0}) moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([ expected_moves =
{1, 2}, {2, 1} Enum.sort([
]) {1, 2},
{2, 1}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
test "knights are blocked by other pieces of the same colour" do test "knights are blocked by other pieces of the same colour" do
board = %{ board = %{
"0,0" => %{"type" => "knight", "colour" => "white"}, "0,0" => %{"type" => "knight", "colour" => "white"},
"1,2" => %{"type" => "king", "colour" => "white"}, "1,2" => %{"type" => "king", "colour" => "white"}
} }
moves = Moves.available(board, {0, 0}) moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([ expected_moves =
{2, 1}, Enum.sort([
]) {2, 1}
])
assert Enum.sort(moves) == expected_moves assert Enum.sort(moves) == expected_moves
end end
def board do def board do
Chess.Board.default Chess.Board.default()
end end
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 test "pawn is blocked from moving two squares by another piece" do
board = %{ board = %{
"4,1" => %{"type" => "pawn", "colour" => "white"}, "4,1" => %{"type" => "pawn", "colour" => "white"},
"4,3" => %{"type" => "pawn", "colour" => "black"}, "4,3" => %{"type" => "pawn", "colour" => "black"}
} }
moves = Moves.available(board, {4, 1}) moves = Moves.available(board, {4, 1})
expected_moves = [{4, 2}] 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 test "pawn is blocked from moving one or two squares by another piece" do
board = %{ board = %{
"4,1" => %{"type" => "pawn", "colour" => "white"}, "4,1" => %{"type" => "pawn", "colour" => "white"},
"4,2" => %{"type" => "pawn", "colour" => "black"}, "4,2" => %{"type" => "pawn", "colour" => "black"}
} }
moves = Moves.available(board, {4, 1}) moves = Moves.available(board, {4, 1})
expected_moves = [] expected_moves = []
@ -58,8 +60,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "pawn is blocked from moving one square by another piece" do test "pawn is blocked from moving one square by another piece" do
board = %{ board = %{
"4,2" => %{"type" => "pawn", "colour" => "white"}, "4,2" => %{"type" => "pawn", "colour" => "white"},
"4,3" => %{"type" => "pawn", "colour" => "black"}, "4,3" => %{"type" => "pawn", "colour" => "black"}
} }
moves = Moves.available(board, {4, 2}) moves = Moves.available(board, {4, 2})
expected_moves = [] expected_moves = []
@ -69,8 +72,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "white pawn can take an opponents piece" do test "white pawn can take an opponents piece" do
board = %{ board = %{
"4,2" => %{"type" => "pawn", "colour" => "white"}, "4,2" => %{"type" => "pawn", "colour" => "white"},
"5,3" => %{"type" => "pawn", "colour" => "black"}, "5,3" => %{"type" => "pawn", "colour" => "black"}
} }
moves = Moves.available(board, {4, 2}) moves = Moves.available(board, {4, 2})
expected_moves = [{4, 3}, {5, 3}] 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 test "black pawn can take an opponents piece" do
board = %{ board = %{
"6,6" => %{"type" => "pawn", "colour" => "black"}, "6,6" => %{"type" => "pawn", "colour" => "black"},
"5,5" => %{"type" => "pawn", "colour" => "white"}, "5,5" => %{"type" => "pawn", "colour" => "white"}
} }
moves = Moves.available(board, {6, 6}) moves = Moves.available(board, {6, 6})
expected_moves = [{6, 5}, {6, 4}, {5, 5}] expected_moves = [{6, 5}, {6, 4}, {5, 5}]
@ -89,6 +94,6 @@ defmodule Chess.Moves.Pieces.PawnTest do
end end
def default_board do def default_board do
Chess.Board.default Chess.Board.default()
end end
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ defmodule ChessWeb.SessionControllerTest do
use ChessWeb.ConnCase use ChessWeb.ConnCase
test "shows log in form", %{conn: conn} do 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" assert html_response(conn, 200) =~ "Log in"
end end
end end

View File

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

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