├── .dockerignore ├── .formatter.exs ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── config ├── config.exs ├── dev.exs ├── prod.exs ├── runtime.exs └── test.exs ├── coveralls.json ├── fly.toml ├── lib ├── app.ex ├── app │ └── application.ex ├── app_web.ex └── app_web │ ├── components │ ├── core_components.ex │ ├── layouts.ex │ └── layouts │ │ ├── app.html.heex │ │ └── root.html.heex │ ├── controllers │ ├── error_html.ex │ ├── error_json.ex │ ├── github_auth_controller.ex │ ├── github_auth_html.ex │ ├── github_auth_html │ │ └── welcome.html.heex │ ├── page_controller.ex │ ├── page_html.ex │ └── page_html │ │ └── home.html.heex │ ├── endpoint.ex │ ├── router.ex │ └── telemetry.ex ├── mix.exs ├── mix.lock ├── priv └── repo │ └── seeds.exs ├── rel └── overlays │ └── bin │ ├── migrate │ ├── migrate.bat │ ├── server │ └── server.bat └── test ├── app_web └── controllers │ ├── error_html_test.exs │ ├── error_json_test.exs │ ├── github_auth_controller_test.exs │ └── page_controller_test.exs ├── support └── conn_case.ex └── test_helper.exs /.dockerignore: -------------------------------------------------------------------------------- 1 | # flyctl launch added from .elixir_ls/.gitignore 2 | .elixir_ls/**/* 3 | 4 | # flyctl launch added from .gitignore 5 | # The directory Mix will write compiled artifacts to. 6 | _build 7 | 8 | # If you run "mix test --cover", coverage assets end up here. 9 | cover 10 | 11 | # The directory Mix downloads your dependencies sources to. 12 | deps 13 | 14 | # Where 3rd-party dependencies like ExDoc output generated docs. 15 | doc 16 | 17 | # Ignore .fetch files in case you like to edit your project deps locally. 18 | .fetch 19 | 20 | # If the VM crashes, it generates a dump, let's ignore it too. 21 | **/erl_crash.dump 22 | 23 | # Also ignore archive artifacts (built via "mix archive.build"). 24 | **/*.ez 25 | 26 | # Ignore package tarball (built via "mix hex.build"). 27 | **/app-*.tar 28 | 29 | # If NPM crashes, it generates a log, let's ignore it too. 30 | **/npm-debug.log 31 | 32 | # The directory NPM downloads your dependencies sources to. 33 | assets/node_modules 34 | 35 | # Since we are building assets from assets/, 36 | # we ignore priv/static. You may want to comment 37 | # this depending on your deployment strategy. 38 | priv/static 39 | 40 | # Files matching config/*.secret.exs pattern contain sensitive 41 | # data and you should not commit them into version control. 42 | # 43 | # Alternatively, you may comment the line below and commit the 44 | # secrets files as long as you replace their contents by environment 45 | # variables. 46 | config/*.secret.exs 47 | 48 | # Erlang Beam compiled files 49 | **/*.beam 50 | **/.elixir_ls 51 | 52 | # .env file stores Environment Variables on Localhost 53 | # see: https://git.io/JeMLg 54 | **/.env 55 | **/.idea 56 | **/.DS_Store 57 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix], 3 | plugins: [Phoenix.LiveView.HTMLFormatter], 4 | inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] 5 | ] 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: mix 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "17:00" 8 | timezone: Europe/London 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Elixir CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | name: Build and test 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | otp: ['25.1.2'] 16 | elixir: ['1.14.2'] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Elixir 20 | uses: erlef/setup-beam@v1 21 | with: 22 | otp-version: ${{ matrix.otp }} 23 | elixir-version: ${{ matrix.elixir }} 24 | - name: Restore deps and _build cache 25 | uses: actions/cache@v3 26 | with: 27 | path: | 28 | deps 29 | _build 30 | key: deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }} 31 | restore-keys: | 32 | deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }} 33 | - name: Install dependencies 34 | run: mix deps.get 35 | - name: Check code is formatted 36 | run: mix format --check-formatted 37 | - name: Run Tests 38 | run: mix coveralls.json 39 | env: 40 | MIX_ENV: test 41 | - name: Upload coverage to Codecov 42 | uses: codecov/codecov-action@v4 43 | with: 44 | token: ${{ secrets.CODECOV_TOKEN }} 45 | 46 | # Continuous Deployment to Fly.io 47 | # https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/ 48 | deploy: 49 | name: Deploy app 50 | runs-on: ubuntu-latest 51 | needs: build 52 | # https://stackoverflow.com/questions/58139406/only-run-job-on-specific-branch-with-github-actions 53 | if: github.ref == 'refs/heads/main' 54 | env: 55 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 56 | steps: 57 | - uses: actions/checkout@v2 58 | - uses: superfly/flyctl-actions@1.1 59 | with: 60 | args: "deploy" 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | app-*.tar 24 | 25 | # If NPM crashes, it generates a log, let's ignore it too. 26 | npm-debug.log 27 | 28 | # The directory NPM downloads your dependencies sources to. 29 | /assets/node_modules/ 30 | 31 | # Since we are building assets from assets/, 32 | # we ignore priv/static. You may want to comment 33 | # this depending on your deployment strategy. 34 | /priv/static/ 35 | 36 | # Files matching config/*.secret.exs pattern contain sensitive 37 | # data and you should not commit them into version control. 38 | # 39 | # Alternatively, you may comment the line below and commit the 40 | # secrets files as long as you replace their contents by environment 41 | # variables. 42 | /config/*.secret.exs 43 | 44 | # Erlang Beam compiled files 45 | *.beam 46 | .elixir_ls/ 47 | 48 | # .env file stores Environment Variables on Localhost 49 | # see: https://git.io/JeMLg 50 | .env 51 | .idea 52 | .DS_Store 53 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian instead of 2 | # Alpine to avoid DNS resolution issues in production. 3 | # 4 | # https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu 5 | # https://hub.docker.com/_/ubuntu?tab=tags 6 | # 7 | # 8 | # This file is based on these images: 9 | # 10 | # - https://hub.docker.com/r/hexpm/elixir/tags - for the build image 11 | # - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20220801-slim - for the release image 12 | # - https://pkgs.org/ - resource for finding needed packages 13 | # - Ex: hexpm/elixir:1.14.1-erlang-25.1.1-debian-bullseye-20220801-slim 14 | # 15 | ARG ELIXIR_VERSION=1.14.1 16 | ARG OTP_VERSION=25.1.1 17 | ARG DEBIAN_VERSION=bullseye-20220801-slim 18 | 19 | ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" 20 | ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" 21 | 22 | FROM ${BUILDER_IMAGE} as builder 23 | 24 | # install build dependencies 25 | RUN apt-get update -y && apt-get install -y build-essential git \ 26 | && apt-get clean && rm -f /var/lib/apt/lists/*_* 27 | 28 | # prepare build dir 29 | WORKDIR /app 30 | 31 | # install hex + rebar 32 | RUN mix local.hex --force && \ 33 | mix local.rebar --force 34 | 35 | # set build ENV 36 | ENV MIX_ENV="prod" 37 | 38 | # install mix dependencies 39 | COPY mix.exs mix.lock ./ 40 | RUN mix deps.get --only $MIX_ENV 41 | RUN mkdir config 42 | 43 | # copy compile-time config files before we compile dependencies 44 | # to ensure any relevant config change will trigger the dependencies 45 | # to be re-compiled. 46 | COPY config/config.exs config/${MIX_ENV}.exs config/ 47 | RUN mix deps.compile 48 | 49 | COPY priv priv 50 | 51 | COPY lib lib 52 | 53 | # Compile the release 54 | RUN mix compile 55 | 56 | # Changes to config/runtime.exs don't require recompiling the code 57 | COPY config/runtime.exs config/ 58 | 59 | COPY rel rel 60 | RUN mix release 61 | 62 | # start a new build stage so that the final image will only contain 63 | # the compiled release and other runtime necessities 64 | FROM ${RUNNER_IMAGE} 65 | 66 | RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales \ 67 | && apt-get clean && rm -f /var/lib/apt/lists/*_* 68 | 69 | # Set the locale 70 | RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen 71 | 72 | ENV LANG en_US.UTF-8 73 | ENV LANGUAGE en_US:en 74 | ENV LC_ALL en_US.UTF-8 75 | 76 | WORKDIR "/app" 77 | RUN chown nobody /app 78 | 79 | # set runner ENV 80 | ENV MIX_ENV="prod" 81 | 82 | # Only copy the final release from the build stage 83 | COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/app ./ 84 | 85 | USER nobody 86 | 87 | CMD ["/app/bin/server"] 88 | # Appended by flyctl 89 | ENV ECTO_IPV6 true 90 | ENV ERL_AFLAGS "-proto_dist inet6_tcp" 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # `elixir-auth-github` _demo_ 4 | 5 | A basic example/tutorial showing **GitHub OAuth** in a **`Phoenix` App** 6 | using [**`elixir-auth-github`**](https://github.com/dwyl/elixir-auth-github). 7 | 8 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dwyl/elixir-auth-github-demo/ci.yml?label=build&style=flat-square&branch=main) 9 | [![codecov.io](https://img.shields.io/codecov/c/github/dwyl/elixir-auth-github-demo/main.svg?style=flat-square)](http://codecov.io/github/dwyl/elixir-auth-github-demo?branch=main) 10 | [![Hex.pm](https://img.shields.io/hexpm/v/elixir_auth_github?color=brightgreen&style=flat-square)](https://hex.pm/packages/elixir_auth_github) 11 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](https://github.com/dwyl/elixir-auth-github/issues) 12 | [![HitCount](https://hits.dwyl.com/dwyl/app-elixir-auth-github-demo.svg)](https://hits.dwyl.com/dwyl/app-elixir-auth-github-demo) 13 | 14 | Try it: 15 | [elixir-auth-github-demo.fly.dev](https://elixir-auth-github-demo.fly.dev/) 16 | 17 | 18 | 19 | No data is stored. 20 | Try and break it. 21 | Open an issue if you spot something. 22 | 23 |
24 | 25 | - [`elixir-auth-github` _demo_](#elixir-auth-github-demo) 26 | - [_Why_? 🤷](#why-) 27 | - [_What_? 💭](#what-) 28 | - [_Who_? 👥](#who-) 29 | - [_How?_ 💻](#how-) 30 | - [0. Create a New Phoenix App](#0-create-a-new-phoenix-app) 31 | - [1. Add the `elixir_auth_github` package to `mix.exs` 📦](#1-add-the-elixir_auth_github-package-to-mixexs-) 32 | - [2. Create the GitHub OAuth Application and Get Credentials ✨](#2-create-the-github-oauth-application-and-get-credentials-) 33 | - [3. Create 3 New Files ➕](#3-create-3-new-files--) 34 | - [3.1 Create a `GithubAuthController` in your Project](#31-create-a-githubauthcontroller-in-your-project) 35 | - [3.2 Create `welcome` template 📝](#32-create-welcome-template-) 36 | - [3.3 Create the `github_auth_html.ex` file](#33-create-the-github_auth_htmlex-file) 37 | - [4. Add the `/auth/github/callback` to `router.ex`](#4-add-the-authgithubcallback-to-routerex) 38 | - [5. Update `PageController.index`](#5-update-pagecontrollerindex) 39 | - [5.1 Update the `page/index.html.eex` Template](#51-update-the-pageindexhtmleex-template) 40 | - [6. _Run_ the App!](#6-run-the-app) 41 | - [_Deployment_?](#deployment) 42 | - [Deploy to Fly.io](#deploy-to-flyio) 43 | 44 | 45 | 46 | # _Why_? 🤷 47 | 48 | **We _love_** having _**detailed docs** and **examples**_ 49 | that explain _exactly_ how to get up-and-running. 50 | We _write_ examples because we want them for _ourselves_, 51 | if you find them useful, please ⭐️ the repo to let us know. 52 | 53 | 54 | # _What_? 💭 55 | 56 | This project is a _barebones_ demo 57 | of using 58 | [`elixir-auth-github`](https://github.com/dwyl/elixir-auth-github) 59 | to add "***Sign-in with GitHub***" support 60 | to any Phoenix App. 61 | 62 | # _Who_? 👥 63 | 64 | This demos is intended for people of all Elixir/Phoenix skill levels. 65 | Anyone who wants the "***Sign-in with GitHub***" functionality 66 | without the extra steps to configure a whole auth _framework_. 67 | 68 | Following all the steps in this example should take around 10 minutes. 69 | However if you get stuck, please don't suffer in silence! 70 | Get help by opening an issue: 71 | [dwyl/elixir-auth-github/issues](https://github.com/dwyl/elixir-auth-github/issues) 72 | 73 | # _How?_ 💻 74 | 75 | This example follows the step-by-instructions in the docs 76 | [dwyl/elixir-auth-github](https://github.com/dwyl/elixir-auth-github) 77 | 78 | 79 | ## 0. Create a New Phoenix App 80 | 81 | Create a new project if you don't already have one: 82 | 83 | > _If you're adding `elixir_auth_github` to an **existing app**, 84 | you can **skip this step**.
85 | Just make sure your app is in a known working state before proceeding_. 86 | 87 | ``` 88 | mix phx.new app 89 | ``` 90 | 91 | > **Note**: In creating this demo app 92 | > we ran the command with the following 93 | > [**flags**](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.New.html):
94 | > `mix phx.new app --no-assets --no-dashboard --no-ecto --no-gettext --no-mailer`
95 | > to keep the project as basic as possible. 96 | > You may need some or all of the features of `Phoenix`, 97 | > so check which flags are applicable to you. 98 | 99 | If prompted to install dependencies: 100 | 101 | ``` 102 | Fetch and install dependencies? [Yn] 103 | ``` 104 | 105 | Type `y` and hit the `[Enter]` key to install. 106 | 107 | You should see something like this: 108 | 109 | ``` 110 | * running mix deps.get 111 | * running mix deps.compile 112 | ``` 113 | 114 | Make sure that everything works before proceeding: 115 | ``` 116 | mix test 117 | ``` 118 | You should see: 119 | ``` 120 | Generated app app 121 | ... 122 | 123 | Finished in 0.02 seconds 124 | 3 tests, 0 failures 125 | ``` 126 | The default tests pass and you know phoenix is compiling. 127 | 128 | Run the web application: 129 | 130 | ``` 131 | mix phx.server 132 | ``` 133 | 134 | and visit the endpoint in your web browser: http://localhost:4000/ 135 | ![phoenix-default-home](https://github.com/dwyl/elixir-auth-github-demo/assets/194400/3912c4af-b6e4-469c-be9f-7b5cdb3fee18) 136 | 137 | 138 | 139 | ## 1. Add the `elixir_auth_github` package to `mix.exs` 📦 140 | 141 | Open your `mix.exs` file and add the following line to your `deps` list: 142 | 143 | ```elixir 144 | def deps do 145 | [ 146 | {:elixir_auth_github, "~> 1.6.5"} 147 | ] 148 | end 149 | ``` 150 | Run the **`mix deps.get`** command to download. 151 | 152 | 153 | 154 | ## 2. Create the GitHub OAuth Application and Get Credentials ✨ 155 | 156 | Create your GitHub App and download the API keys 157 | by follow the instructions in: 158 | [`/create-github-app-guide.md`](https://github.com/dwyl/elixir-auth-github/blob/master/create-github-app-guide.md) 159 | 160 | By the end of this step 161 | you should have these two environment variables defined: 162 | 163 | ```yml 164 | GITHUB_CLIENT_ID=631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt 165 | GITHUB_CLIENT_SECRET=MHxv6-RGF5nheXnxh1b0LNDq 166 | ``` 167 | 168 | > ⚠️ Don't worry, these keys aren't valid. 169 | They are just here for illustration purposes. 170 | 171 | ## 3. Create 3 New Files ➕ 172 | 173 | We need to create two files in order to handle the requests 174 | to the GitHub OAuth API and display data to people using our app. 175 | 176 | ### 3.1 Create a `GithubAuthController` in your Project 177 | 178 | In order to process and _display_ the data returned by the GitHub OAuth2 API, 179 | we need to create a new `controller`. 180 | 181 | Create a new file called 182 | `lib/app_web/controllers/github_auth_controller.ex` 183 | 184 | and add the following code: 185 | 186 | ```elixir 187 | defmodule AppWeb.GithubAuthController do 188 | use AppWeb, :controller 189 | 190 | @doc """ 191 | `index/2` handles the callback from GitHub Auth API redirect. 192 | """ 193 | def index(conn, %{"code" => code}) do 194 | {:ok, profile} = ElixirAuthGithub.github_auth(code) 195 | render(conn, :welcome, [layout: false, profile: profile]) 196 | end 197 | end 198 | 199 | ``` 200 | 201 | This code does 3 things: 202 | + Create a one-time auth token based on the response `code` sent by GitHub 203 | after the person authenticates. 204 | + Request the person's profile data from GitHub based on an `access_token` 205 | + Renders a `:welcome` view displaying some profile data 206 | to confirm that login with GitHub was successful. 207 | 208 | > Note: we are placing the `welcome.html.eex` template 209 | in the `template/page` directory to save having to create 210 | any more directories and view files. 211 | You are free to organise your code however you prefer. 212 | 213 | ### 3.2 Create `welcome` template 📝 214 | 215 | Create a new file with the following path: 216 | `lib/app_web/controllers/github_auth_html/welcome.html.heex` 217 | 218 | And type (_or paste_) the following code in it: 219 | ```html 220 | 221 | 222 |
223 |

224 | Welcome <%= @profile.name %>! 225 | avatar image 227 |

228 |

You are signed in 229 | with your GitHub Account
230 | <%= @profile.email %> 231 |

232 |
233 | ``` 234 | 235 | > **Note**: There's a fair amount of `TailwindCSS` sprinkled in that template, 236 | if you're new to `Tailwind` or need a refresher, 237 | please see: 238 | [/learn-tailwind](https://github.com/dwyl/learn-tailwind) 239 | 240 | 241 | Invoking `ElixirAuthGithub.github_auth(code)` 242 | in the `GithubAuthController` 243 | `index` function will 244 | make an HTTP request to the GitHub Auth API 245 | and will return `{:ok, profile}` 246 | where the `profile` data 247 | has the following format: 248 | 249 | ```elixir 250 | %{ 251 | followers_url: "https://api.github.com/users/nelsonic/followers", 252 | public_repos: 291, 253 | plan: %{ 254 | "collaborators" => 0, 255 | "name" => "pro", 256 | "private_repos" => 9999, 257 | "space" => 976562499 258 | }, 259 | created_at: "2010-02-02T08:44:49Z", 260 | name: "Nelson", 261 | company: "@dwyl", 262 | email: "nelson@gmail.com", 263 | two_factor_authentication: true, 264 | starred_url: "https://api.github.com/users/nelsonic/starred{/owner}{/repo}", 265 | id: 194400, 266 | following: 173, 267 | login: "nelsonic", 268 | collaborators: 28, 269 | avatar_url: "https://avatars3.githubusercontent.com/u/194400?v=4", 270 | etc: "you get the idea ..." 271 | } 272 | ``` 273 | 274 | More info: https://developer.github.com/v3/users 275 | 276 | Use this data how you see fit. 277 | (_obviously treat it with respect, 278 | only store what you need and keep it secure!_) 279 | 280 | ### 3.3 Create the `github_auth_html.ex` file 281 | 282 | Create a file with the path: 283 | `lib/app_web/controllers/github_auth_html.ex` 284 | 285 | and add the following code to it: 286 | 287 | ```elixir 288 | defmodule AppWeb.GithubAuthHTML do 289 | use AppWeb, :html 290 | 291 | embed_templates "github_auth_html/*" 292 | end 293 | ``` 294 | 295 | This is required so `Phoenix` knows where to find the template. 296 | 297 | 298 | ## 4. Add the `/auth/github/callback` to `router.ex` 299 | 300 | Open your `lib/app_web/router.ex` file 301 | and locate the section that looks like `scope "/", AppWeb do` 302 | 303 | Add the following line: 304 | 305 | ```elixir 306 | get "/auth/github/callback", GithubAuthController, :index 307 | ``` 308 | 309 | That will direct the API request response 310 | to the `GithubAuthController` `:index` function we defined above. 311 | 312 | 313 | ## 5. Update `PageController.index` 314 | 315 | In order to display the "Sign-in with GitHub" button in the UI, 316 | we need to _generate_ the URL for the button in the relevant controller, 317 | and pass it to the template. 318 | 319 | Open the `lib/app_web/controllers/page_controller.ex` file 320 | and update the `index` function: 321 | 322 | From: 323 | ```elixir 324 | def home(conn, _params) do 325 | # The home page is often custom made, 326 | # so skip the default app layout. 327 | render(conn, :home, layout: false) 328 | end 329 | ``` 330 | 331 | To: 332 | ```elixir 333 | def home(conn, _params) do 334 | oauth_github_url = ElixirAuthGithub.login_url_with_scope(["user:email"]) 335 | render(conn, :home, [layout: false, oauth_github_url: oauth_github_url]) 336 | end 337 | ``` 338 | 339 | ### 5.1 Update the `page/index.html.eex` Template 340 | 341 | Open the `/lib/app_web/controllers/page_html/home.html.heex` file 342 | and type (_or paste_) the following code: 343 | 344 | ```html 345 | 346 | 347 |
348 |
349 |
350 |
351 |

352 | Welcome to Awesome App! 353 |

354 |

355 | To get started, login with your GitHub Account: 356 |

357 | 358 | Sign in with GitHub 359 | 360 |
361 |
362 |
363 |
364 | ``` 365 | 366 | > **Note**: the login button is an image for brevity. 367 | > In our production version we use `CSS` and `SVG`, 368 | > see: 369 | > [/elixir-auth-github#optimised-svgcss-button](https://github.com/dwyl/elixir-auth-github#optimised-svgcss-button) 370 | 371 | ## 6. _Run_ the App! 372 | 373 | Run the app with the command: 374 | 375 | ```sh 376 | mix phx.server 377 | ``` 378 | 379 | Visit the home page of the app 380 | where you will see a 381 | "Sign in with GitHub" button: 382 | http://localhost:4000 383 | 384 | ![sign-in-button](https://github.com/dwyl/elixir-auth-github-demo/assets/194400/8942e6a1-6b5a-4e09-99cb-7924e0631acd) 385 | 386 | Once the user authorizes the App, 387 | they will be redirected 388 | back to the Phoenix App 389 | and will see welcome message: 390 | 391 | ![welcome](https://github.com/dwyl/elixir-auth-github-demo/assets/194400/c1a8bf8e-e1ed-4d69-8b1e-93b2ad6ca003) 392 | 393 | 394 |
395 | 396 | 397 | ## _Deployment_? 398 | 399 | This guide is meant to get your `Phoenix` App up-and-running 400 | with [elixir-auth-github](https://github.com/dwyl/elixir-auth-github) 401 | on **`localhost`**. 402 | 403 | The demo is deployed to Fly.io 404 | to demonstrate that everything works as expected: 405 | 406 | 407 | No data is saved by the demo app, 408 | so feel free to try an _break_ it! 409 | 410 | https://elixir-auth-github-demo.fly.dev 411 | 412 | 413 | 414 | Authorization screen: 415 | 416 | image 417 | 418 | Welcome (success): 419 | 420 | 421 | 422 | 423 | ### Deploy to Fly.io 424 | 425 | If you want to deploy your own `Phoenix` App to Fly.io, 426 | simply follow the official `Elixir` Getting Started guide: 427 | [fly.io/docs/elixir/getting-started](https://fly.io/docs/elixir/getting-started/) 428 | 429 | ```sh 430 | fly launch 431 | ``` 432 | 433 | Speed through the prompts to create the App 434 | and then add the add the 3 required environment variables: 435 | 436 | ```sh 437 | fly secrets set GITHUB_CLIENT_ID=4458109151751aetc 438 | fly secrets set GITHUB_CLIENT_SECRET=256df107df6454001a90d667fetc 439 | SECRET_KEY_BASE=fephli94y1u1X7F8Snh9RUvz5l0fd1ySaz9WtzaUAX+NmfB0uE2xwetc 440 | ``` 441 | 442 | > **Note**: _none_ of these keys are valid. 443 | > They are just for illustration purposes. 444 | > Follow the instructions: 445 | > [dwyl/elixir-auth-google/blob/main/create-google-app-guide.md](https://github.com/dwyl/elixir-auth-google/blob/main/create-google-app-guide.md) 446 | > to get your Google App keys. 447 | 448 | Refer to the 449 | `Dockerfile` 450 | and 451 | `fly.toml` 452 | in this demo project 453 | if you need an example. 454 | 455 | Recommended reading: 456 | "Deploying with Releases" 457 | [hexdocs.pm/phoenix/releases.html](https://hexdocs.pm/phoenix/releases.html) 458 | 459 | For Continuous Deployment to Fly.io, 460 | read: 461 | [fly.io/docs/app-guides/continuous-deployment-with-github-actions](https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/) 462 | 463 | 464 | 465 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | 7 | # General application configuration 8 | import Config 9 | 10 | config :app, 11 | generators: [timestamp_type: :utc_datetime] 12 | 13 | # Configures the endpoint 14 | config :app, AppWeb.Endpoint, 15 | url: [host: "localhost"], 16 | adapter: Phoenix.Endpoint.Cowboy2Adapter, 17 | render_errors: [ 18 | formats: [html: AppWeb.ErrorHTML, json: AppWeb.ErrorJSON], 19 | layout: false 20 | ], 21 | pubsub_server: App.PubSub, 22 | live_view: [signing_salt: "xcoMLupd"] 23 | 24 | # Configures Elixir's Logger 25 | config :logger, :console, 26 | format: "$time $metadata[$level] $message\n", 27 | metadata: [:request_id] 28 | 29 | # Use Jason for JSON parsing in Phoenix 30 | config :phoenix, :json_library, Jason 31 | 32 | # Import environment specific config. This must remain at the bottom 33 | # of this file so it overrides the configuration defined above. 34 | import_config "#{config_env()}.exs" 35 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For development, we disable any cache and enable 4 | # debugging and code reloading. 5 | # 6 | # The watchers configuration can be used to run external 7 | # watchers to your application. For example, we can use it 8 | # to bundle .js and .css sources. 9 | config :app, AppWeb.Endpoint, 10 | # Binding to loopback ipv4 address prevents access from other machines. 11 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. 12 | http: [ip: {127, 0, 0, 1}, port: 4000], 13 | check_origin: false, 14 | code_reloader: true, 15 | debug_errors: true, 16 | secret_key_base: "KI3E1INmNOHt1AIs1yWNeIIM4enQ+agHE9tXvqTuNHJEkUgWoRkyzNFdYRy/9Ge7", 17 | watchers: [] 18 | 19 | # ## SSL Support 20 | # 21 | # In order to use HTTPS in development, a self-signed 22 | # certificate can be generated by running the following 23 | # Mix task: 24 | # 25 | # mix phx.gen.cert 26 | # 27 | # Run `mix help phx.gen.cert` for more information. 28 | # 29 | # The `http:` config above can be replaced with: 30 | # 31 | # https: [ 32 | # port: 4001, 33 | # cipher_suite: :strong, 34 | # keyfile: "priv/cert/selfsigned_key.pem", 35 | # certfile: "priv/cert/selfsigned.pem" 36 | # ], 37 | # 38 | # If desired, both `http:` and `https:` keys can be 39 | # configured to run both http and https servers on 40 | # different ports. 41 | 42 | # Watch static and templates for browser reloading. 43 | config :app, AppWeb.Endpoint, 44 | live_reload: [ 45 | patterns: [ 46 | ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", 47 | ~r"lib/app_web/(controllers|live|components)/.*(ex|heex)$" 48 | ] 49 | ] 50 | 51 | # Enable dev routes for dashboard and mailbox 52 | config :app, dev_routes: true 53 | 54 | # Do not include metadata nor timestamps in development logs 55 | config :logger, :console, format: "[$level] $message\n" 56 | 57 | # Set a higher stacktrace during development. Avoid configuring such 58 | # in production as building large stacktraces may be expensive. 59 | config :phoenix, :stacktrace_depth, 20 60 | 61 | # Initialize plugs at runtime for faster development compilation 62 | config :phoenix, :plug_init_mode, :runtime 63 | 64 | # Include HEEx debug annotations as HTML comments in rendered markup 65 | config :phoenix_live_view, :debug_heex_annotations, true 66 | -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Do not print debug messages in production 4 | config :logger, level: :info 5 | 6 | # Runtime production configuration, including reading 7 | # of environment variables, is done on config/runtime.exs. 8 | -------------------------------------------------------------------------------- /config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # config/runtime.exs is executed for all environments, including 4 | # during releases. It is executed after compilation and before the 5 | # system starts, so it is typically used to load production configuration 6 | # and secrets from environment variables or elsewhere. Do not define 7 | # any compile-time configuration in here, as it won't be applied. 8 | # The block below contains prod specific runtime configuration. 9 | 10 | # ## Using releases 11 | # 12 | # If you use `mix release`, you need to explicitly enable the server 13 | # by passing the PHX_SERVER=true when you start it: 14 | # 15 | # PHX_SERVER=true bin/app start 16 | # 17 | # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` 18 | # script that automatically sets the env var above. 19 | if System.get_env("PHX_SERVER") do 20 | config :app, AppWeb.Endpoint, server: true 21 | end 22 | 23 | if config_env() == :prod do 24 | # The secret key base is used to sign/encrypt cookies and other secrets. 25 | # A default value is used in config/dev.exs and config/test.exs but you 26 | # want to use a different value for prod and you most likely don't want 27 | # to check this value into version control, so we use an environment 28 | # variable instead. 29 | secret_key_base = 30 | System.get_env("SECRET_KEY_BASE") || 31 | raise """ 32 | environment variable SECRET_KEY_BASE is missing. 33 | You can generate one by calling: mix phx.gen.secret 34 | """ 35 | 36 | host = System.get_env("PHX_HOST") || "example.com" 37 | port = String.to_integer(System.get_env("PORT") || "4000") 38 | 39 | config :app, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") 40 | 41 | config :app, AppWeb.Endpoint, 42 | url: [host: host, port: 443, scheme: "https"], 43 | http: [ 44 | # Enable IPv6 and bind on all interfaces. 45 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. 46 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html 47 | # for details about using IPv6 vs IPv4 and loopback vs public addresses. 48 | ip: {0, 0, 0, 0, 0, 0, 0, 0}, 49 | port: port 50 | ], 51 | secret_key_base: secret_key_base 52 | 53 | # ## SSL Support 54 | # 55 | # To get SSL working, you will need to add the `https` key 56 | # to your endpoint configuration: 57 | # 58 | # config :app, AppWeb.Endpoint, 59 | # https: [ 60 | # ..., 61 | # port: 443, 62 | # cipher_suite: :strong, 63 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 64 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 65 | # ] 66 | # 67 | # The `cipher_suite` is set to `:strong` to support only the 68 | # latest and more secure SSL ciphers. This means old browsers 69 | # and clients may not be supported. You can set it to 70 | # `:compatible` for wider support. 71 | # 72 | # `:keyfile` and `:certfile` expect an absolute path to the key 73 | # and cert in disk or a relative path inside priv, for example 74 | # "priv/ssl/server.key". For all supported SSL configuration 75 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 76 | # 77 | # We also recommend setting `force_ssl` in your endpoint, ensuring 78 | # no data is ever sent via http, always redirecting to https: 79 | # 80 | # config :app, AppWeb.Endpoint, 81 | # force_ssl: [hsts: true] 82 | # 83 | # Check `Plug.SSL` for all available options in `force_ssl`. 84 | end 85 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # We don't run a server during test. If one is required, 4 | # you can enable the server option below. 5 | config :app, AppWeb.Endpoint, 6 | http: [ip: {127, 0, 0, 1}, port: 4002], 7 | secret_key_base: "Hgwn7B7v8EcxKAGpJ65aTAmpz2dKRTUGG3q1wquHD88VG4Tfm0IyH8bJvUsqNLSC", 8 | server: false 9 | 10 | # Print only warnings and errors during test 11 | config :logger, level: :warning 12 | 13 | # Initialize plugs at runtime for faster test compilation 14 | config :phoenix, :plug_init_mode, :runtime 15 | 16 | config :elixir_auth_github, 17 | client_id: "d6fca75c63daa014c187", 18 | client_secret: "8eeb143935d1a505692aaef856db9b4da8245f3c", 19 | httpoison_mock: true 20 | -------------------------------------------------------------------------------- /coveralls.json: -------------------------------------------------------------------------------- 1 | { 2 | "skip_files": [ 3 | "test/", 4 | "lib/app.ex", 5 | "lib/app/application.ex", 6 | "lib/app_web.ex", 7 | "lib/app/repo.ex", 8 | "lib/app/release.ex", 9 | "lib/app_web/gettext.ex", 10 | "lib/app_web/channels/user_socket.ex", 11 | "lib/app_web/views/app_view.ex", 12 | "lib/app_web/views/init_view.ex", 13 | "lib/app_web/views/layout_view.ex", 14 | "lib/app_web/views/error_helpers.ex", 15 | "lib/app_web/endpoint.ex", 16 | "lib/app_web/telemetry.ex", 17 | "lib/app_web/components/core_components.ex" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml file generated for elixir-auth-github-demo on 2022-12-10T08:53:41Z 2 | 3 | app = "elixir-auth-github-demo" 4 | kill_signal = "SIGTERM" 5 | kill_timeout = 5 6 | processes = [] 7 | 8 | [env] 9 | PHX_HOST = "elixir-auth-github-demo.fly.dev" 10 | PORT = "8080" 11 | 12 | [experimental] 13 | allowed_public_ports = [] 14 | auto_rollback = true 15 | 16 | [[services]] 17 | http_checks = [] 18 | internal_port = 8080 19 | processes = ["app"] 20 | protocol = "tcp" 21 | script_checks = [] 22 | [services.concurrency] 23 | hard_limit = 25 24 | soft_limit = 20 25 | type = "connections" 26 | 27 | [[services.ports]] 28 | force_https = true 29 | handlers = ["http"] 30 | port = 80 31 | 32 | [[services.ports]] 33 | handlers = ["tls", "http"] 34 | port = 443 35 | 36 | [[services.tcp_checks]] 37 | grace_period = "1s" 38 | interval = "15s" 39 | restart_limit = 0 40 | timeout = "2s" 41 | -------------------------------------------------------------------------------- /lib/app.ex: -------------------------------------------------------------------------------- 1 | defmodule App do 2 | @moduledoc """ 3 | App keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | end 10 | -------------------------------------------------------------------------------- /lib/app/application.ex: -------------------------------------------------------------------------------- 1 | defmodule App.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | AppWeb.Telemetry, 12 | {DNSCluster, query: Application.get_env(:app, :dns_cluster_query) || :ignore}, 13 | {Phoenix.PubSub, name: App.PubSub}, 14 | # Start a worker by calling: App.Worker.start_link(arg) 15 | # {App.Worker, arg}, 16 | # Start to serve requests, typically the last entry 17 | AppWeb.Endpoint 18 | ] 19 | 20 | # See https://hexdocs.pm/elixir/Supervisor.html 21 | # for other strategies and supported options 22 | opts = [strategy: :one_for_one, name: App.Supervisor] 23 | Supervisor.start_link(children, opts) 24 | end 25 | 26 | # Tell Phoenix to update the endpoint configuration 27 | # whenever the application is updated. 28 | @impl true 29 | def config_change(changed, _new, removed) do 30 | AppWeb.Endpoint.config_change(changed, removed) 31 | :ok 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/app_web.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, components, channels, and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use AppWeb, :controller 9 | use AppWeb, :html 10 | 11 | The definitions below will be executed for every controller, 12 | component, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define additional modules and import 17 | those modules here. 18 | """ 19 | 20 | def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) 21 | 22 | def router do 23 | quote do 24 | use Phoenix.Router, helpers: false 25 | 26 | # Import common connection and controller functions to use in pipelines 27 | import Plug.Conn 28 | import Phoenix.Controller 29 | import Phoenix.LiveView.Router 30 | end 31 | end 32 | 33 | def channel do 34 | quote do 35 | use Phoenix.Channel 36 | end 37 | end 38 | 39 | def controller do 40 | quote do 41 | use Phoenix.Controller, 42 | formats: [:html, :json], 43 | layouts: [html: AppWeb.Layouts] 44 | 45 | import Plug.Conn 46 | 47 | unquote(verified_routes()) 48 | end 49 | end 50 | 51 | def live_view do 52 | quote do 53 | use Phoenix.LiveView, 54 | layout: {AppWeb.Layouts, :app} 55 | 56 | unquote(html_helpers()) 57 | end 58 | end 59 | 60 | def live_component do 61 | quote do 62 | use Phoenix.LiveComponent 63 | 64 | unquote(html_helpers()) 65 | end 66 | end 67 | 68 | def html do 69 | quote do 70 | use Phoenix.Component 71 | 72 | # Import convenience functions from controllers 73 | import Phoenix.Controller, 74 | only: [get_csrf_token: 0, view_module: 1, view_template: 1] 75 | 76 | # Include general helpers for rendering HTML 77 | unquote(html_helpers()) 78 | end 79 | end 80 | 81 | defp html_helpers do 82 | quote do 83 | # HTML escaping functionality 84 | import Phoenix.HTML 85 | # Core UI components and translation 86 | import AppWeb.CoreComponents 87 | 88 | # Shortcut for generating JS commands 89 | alias Phoenix.LiveView.JS 90 | 91 | # Routes generation with the ~p sigil 92 | unquote(verified_routes()) 93 | end 94 | end 95 | 96 | def verified_routes do 97 | quote do 98 | use Phoenix.VerifiedRoutes, 99 | endpoint: AppWeb.Endpoint, 100 | router: AppWeb.Router, 101 | statics: AppWeb.static_paths() 102 | end 103 | end 104 | 105 | @doc """ 106 | When used, dispatch to the appropriate controller/view/etc. 107 | """ 108 | defmacro __using__(which) when is_atom(which) do 109 | apply(__MODULE__, which, []) 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/app_web/components/core_components.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.CoreComponents do 2 | @moduledoc """ 3 | Provides core UI components. 4 | 5 | At first glance, this module may seem daunting, but its goal is to provide 6 | core building blocks for your application, such as modals, tables, and 7 | forms. The components consist mostly of markup and are well-documented 8 | with doc strings and declarative assigns. You may customize and style 9 | them in any way you want, based on your application growth and needs. 10 | 11 | The default components use Tailwind CSS, a utility-first CSS framework. 12 | See the [Tailwind CSS documentation](https://tailwindcss.com) to learn 13 | how to customize them or feel free to swap in another framework altogether. 14 | 15 | Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. 16 | """ 17 | use Phoenix.Component 18 | 19 | alias Phoenix.LiveView.JS 20 | 21 | @doc """ 22 | Renders a modal. 23 | 24 | ## Examples 25 | 26 | <.modal id="confirm-modal"> 27 | This is a modal. 28 | 29 | 30 | JS commands may be passed to the `:on_cancel` to configure 31 | the closing/cancel event, for example: 32 | 33 | <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}> 34 | This is another modal. 35 | 36 | 37 | """ 38 | attr :id, :string, required: true 39 | attr :show, :boolean, default: false 40 | attr :on_cancel, JS, default: %JS{} 41 | slot :inner_block, required: true 42 | 43 | def modal(assigns) do 44 | ~H""" 45 | 325 | """ 326 | end 327 | 328 | def input(%{type: "select"} = assigns) do 329 | ~H""" 330 |
331 | <.label for={@id}>{@label} 332 | 342 | <.error :for={msg <- @errors}>{msg} 343 |
344 | """ 345 | end 346 | 347 | def input(%{type: "textarea"} = assigns) do 348 | ~H""" 349 |
350 | <.label for={@id}>{@label} 351 | 362 | <.error :for={msg <- @errors}>{msg} 363 |
364 | """ 365 | end 366 | 367 | # All other inputs text, datetime-local, url, password, etc. are handled here... 368 | def input(assigns) do 369 | ~H""" 370 |
371 | <.label for={@id}>{@label} 372 | 385 | <.error :for={msg <- @errors}>{msg} 386 |
387 | """ 388 | end 389 | 390 | @doc """ 391 | Renders a label. 392 | """ 393 | attr :for, :string, default: nil 394 | slot :inner_block, required: true 395 | 396 | def label(assigns) do 397 | ~H""" 398 | 401 | """ 402 | end 403 | 404 | @doc """ 405 | Generates a generic error message. 406 | """ 407 | slot :inner_block, required: true 408 | 409 | def error(assigns) do 410 | ~H""" 411 |

412 | <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> 413 | {render_slot(@inner_block)} 414 |

415 | """ 416 | end 417 | 418 | @doc """ 419 | Renders a header with title. 420 | """ 421 | attr :class, :string, default: nil 422 | 423 | slot :inner_block, required: true 424 | slot :subtitle 425 | slot :actions 426 | 427 | def header(assigns) do 428 | ~H""" 429 |
430 |
431 |

432 | {render_slot(@inner_block)} 433 |

434 |

435 | {render_slot(@subtitle)} 436 |

437 |
438 |
{render_slot(@actions)}
439 |
440 | """ 441 | end 442 | 443 | @doc ~S""" 444 | Renders a table with generic styling. 445 | 446 | ## Examples 447 | 448 | <.table id="users" rows={@users}> 449 | <:col :let={user} label="id"><%= user.id %> 450 | <:col :let={user} label="username"><%= user.username %> 451 | 452 | """ 453 | attr :id, :string, required: true 454 | attr :rows, :list, required: true 455 | attr :row_id, :any, default: nil, doc: "the function for generating the row id" 456 | attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" 457 | 458 | attr :row_item, :any, 459 | default: &Function.identity/1, 460 | doc: "the function for mapping each row before calling the :col and :action slots" 461 | 462 | slot :col, required: true do 463 | attr :label, :string 464 | end 465 | 466 | slot :action, doc: "the slot for showing user actions in the last table column" 467 | 468 | def table(assigns) do 469 | assigns = 470 | with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do 471 | assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) 472 | end 473 | 474 | ~H""" 475 |
476 | 477 | 478 | 479 | 480 | 483 | 484 | 485 | 490 | 491 | 503 | 514 | 515 | 516 |
{col[:label]} 481 | Actions 482 |
496 |
497 | 498 | 499 | {render_slot(col, @row_item.(row))} 500 | 501 |
502 |
504 |
505 | 506 | 510 | {render_slot(action, @row_item.(row))} 511 | 512 |
513 |
517 |
518 | """ 519 | end 520 | 521 | @doc """ 522 | Renders a data list. 523 | 524 | ## Examples 525 | 526 | <.list> 527 | <:item title="Title"><%= @post.title %> 528 | <:item title="Views"><%= @post.views %> 529 | 530 | """ 531 | slot :item, required: true do 532 | attr :title, :string, required: true 533 | end 534 | 535 | def list(assigns) do 536 | ~H""" 537 |
538 |
539 |
540 |
{item.title}
541 |
{render_slot(item)}
542 |
543 |
544 |
545 | """ 546 | end 547 | 548 | @doc """ 549 | Renders a back navigation link. 550 | 551 | ## Examples 552 | 553 | <.back navigate={~p"/posts"}>Back to posts 554 | """ 555 | attr :navigate, :any, required: true 556 | slot :inner_block, required: true 557 | 558 | def back(assigns) do 559 | ~H""" 560 |
561 | <.link 562 | navigate={@navigate} 563 | class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" 564 | > 565 | <.icon name="hero-arrow-left-solid" class="h-3 w-3" /> 566 | {render_slot(@inner_block)} 567 | 568 |
569 | """ 570 | end 571 | 572 | @doc """ 573 | Renders a [Heroicon](https://heroicons.com). 574 | 575 | Heroicons come in three styles – outline, solid, and mini. 576 | By default, the outline style is used, but solid and mini may 577 | be applied by using the `-solid` and `-mini` suffix. 578 | 579 | You can customize the size and colors of the icons by setting 580 | width, height, and background color classes. 581 | 582 | Icons are extracted from your `assets/vendor/heroicons` directory and bundled 583 | within your compiled app.css by the plugin in your `assets/tailwind.config.js`. 584 | 585 | ## Examples 586 | 587 | <.icon name="hero-x-mark-solid" /> 588 | <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" /> 589 | """ 590 | attr :name, :string, required: true 591 | attr :class, :string, default: nil 592 | 593 | def icon(%{name: "hero-" <> _} = assigns) do 594 | ~H""" 595 | 596 | """ 597 | end 598 | 599 | ## JS Commands 600 | 601 | def show(js \\ %JS{}, selector) do 602 | JS.show(js, 603 | to: selector, 604 | transition: 605 | {"transition-all transform ease-out duration-300", 606 | "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", 607 | "opacity-100 translate-y-0 sm:scale-100"} 608 | ) 609 | end 610 | 611 | def hide(js \\ %JS{}, selector) do 612 | JS.hide(js, 613 | to: selector, 614 | time: 200, 615 | transition: 616 | {"transition-all transform ease-in duration-200", 617 | "opacity-100 translate-y-0 sm:scale-100", 618 | "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} 619 | ) 620 | end 621 | 622 | def show_modal(js \\ %JS{}, id) when is_binary(id) do 623 | js 624 | |> JS.show(to: "##{id}") 625 | |> JS.show( 626 | to: "##{id}-bg", 627 | transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"} 628 | ) 629 | |> show("##{id}-container") 630 | |> JS.add_class("overflow-hidden", to: "body") 631 | |> JS.focus_first(to: "##{id}-content") 632 | end 633 | 634 | def hide_modal(js \\ %JS{}, id) do 635 | js 636 | |> JS.hide( 637 | to: "##{id}-bg", 638 | transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"} 639 | ) 640 | |> hide("##{id}-container") 641 | |> JS.hide(to: "##{id}", transition: {"block", "block", "hidden"}) 642 | |> JS.remove_class("overflow-hidden", to: "body") 643 | |> JS.pop_focus() 644 | end 645 | 646 | @doc """ 647 | Translates an error message using gettext. 648 | """ 649 | def translate_error({msg, opts}) do 650 | # You can make use of gettext to translate error messages by 651 | # uncommenting and adjusting the following code: 652 | 653 | # if count = opts[:count] do 654 | # Gettext.dngettext(AppWeb.Gettext, "errors", msg, msg, count, opts) 655 | # else 656 | # Gettext.dgettext(AppWeb.Gettext, "errors", msg, opts) 657 | # end 658 | 659 | Enum.reduce(opts, msg, fn {key, value}, acc -> 660 | String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end) 661 | end) 662 | end 663 | 664 | @doc """ 665 | Translates the errors for a field from a keyword list of errors. 666 | """ 667 | def translate_errors(errors, field) when is_list(errors) do 668 | for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) 669 | end 670 | end 671 | -------------------------------------------------------------------------------- /lib/app_web/components/layouts.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.Layouts do 2 | use AppWeb, :html 3 | 4 | embed_templates "layouts/*" 5 | end 6 | -------------------------------------------------------------------------------- /lib/app_web/components/layouts/app.html.heex: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 |

8 | v{Application.spec(:phoenix, :vsn)} 9 |

10 |
11 | 25 |
26 |
27 |
28 |
29 | <.flash_group flash={@flash} /> 30 | {@inner_content} 31 |
32 |
33 | -------------------------------------------------------------------------------- /lib/app_web/components/layouts/root.html.heex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <.live_title suffix=" · Phoenix Framework"> 8 | {assigns[:page_title] || "App"} 9 | 10 | 11 | 13 | 14 | 15 | {@inner_content} 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/app_web/controllers/error_html.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.ErrorHTML do 2 | use AppWeb, :html 3 | 4 | # If you want to customize your error pages, 5 | # uncomment the embed_templates/1 call below 6 | # and add pages to the error directory: 7 | # 8 | # * lib/app_web/controllers/error_html/404.html.heex 9 | # * lib/app_web/controllers/error_html/500.html.heex 10 | # 11 | # embed_templates "error_html/*" 12 | 13 | # The default is to render a plain text page based on 14 | # the template name. For example, "404.html" becomes 15 | # "Not Found". 16 | def render(template, _assigns) do 17 | Phoenix.Controller.status_message_from_template(template) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/app_web/controllers/error_json.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.ErrorJSON do 2 | # If you want to customize a particular status code, 3 | # you may add your own clauses, such as: 4 | # 5 | # def render("500.json", _assigns) do 6 | # %{errors: %{detail: "Internal Server Error"}} 7 | # end 8 | 9 | # By default, Phoenix returns the status message from 10 | # the template name. For example, "404.json" becomes 11 | # "Not Found". 12 | def render(template, _assigns) do 13 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/app_web/controllers/github_auth_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.GithubAuthController do 2 | use AppWeb, :controller 3 | 4 | @doc """ 5 | `index/2` handles the callback from GitHub Auth API redirect. 6 | """ 7 | def index(conn, %{"code" => code}) do 8 | {:ok, profile} = ElixirAuthGithub.github_auth(code) 9 | render(conn, :welcome, layout: false, profile: profile) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/app_web/controllers/github_auth_html.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.GithubAuthHTML do 2 | use AppWeb, :html 3 | 4 | embed_templates "github_auth_html/*" 5 | end 6 | -------------------------------------------------------------------------------- /lib/app_web/controllers/github_auth_html/welcome.html.heex: -------------------------------------------------------------------------------- 1 | 3 |
4 |

5 | Welcome {@profile.name}! 6 | avatar image 12 |

13 |

You are signed in with your GitHub Account
14 | {@profile.email}

15 |
16 | -------------------------------------------------------------------------------- /lib/app_web/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.PageController do 2 | use AppWeb, :controller 3 | 4 | def home(conn, _params) do 5 | oauth_github_url = ElixirAuthGithub.login_url(%{scopes: ["user:email"]}) 6 | render(conn, :home, layout: false, oauth_github_url: oauth_github_url) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/app_web/controllers/page_html.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.PageHTML do 2 | use AppWeb, :html 3 | 4 | embed_templates "page_html/*" 5 | end 6 | -------------------------------------------------------------------------------- /lib/app_web/controllers/page_html/home.html.heex: -------------------------------------------------------------------------------- 1 | 3 | 4 |
5 |
6 |
7 |
8 |

9 | Welcome to Awesome App! 10 |

11 |

12 | To get started, login with your GitHub Account: 13 |

14 | 15 | Sign in with GitHub 16 | 17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /lib/app_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :app 3 | 4 | # The session will be stored in the cookie and signed, 5 | # this means its contents can be read but not tampered with. 6 | # Set :encryption_salt if you would also like to encrypt it. 7 | @session_options [ 8 | store: :cookie, 9 | key: "_app_key", 10 | signing_salt: "ERITzbwd", 11 | same_site: "Lax" 12 | ] 13 | 14 | socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] 15 | 16 | # Serve at "/" the static files from "priv/static" directory. 17 | # 18 | # You should set gzip to true if you are running phx.digest 19 | # when deploying your static files in production. 20 | plug Plug.Static, 21 | at: "/", 22 | from: :app, 23 | gzip: false, 24 | only: AppWeb.static_paths() 25 | 26 | # Code reloading can be explicitly enabled under the 27 | # :code_reloader configuration of your endpoint. 28 | if code_reloading? do 29 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 30 | plug Phoenix.LiveReloader 31 | plug Phoenix.CodeReloader 32 | end 33 | 34 | plug Plug.RequestId 35 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 36 | 37 | plug Plug.Parsers, 38 | parsers: [:urlencoded, :multipart, :json], 39 | pass: ["*/*"], 40 | json_decoder: Phoenix.json_library() 41 | 42 | plug Plug.MethodOverride 43 | plug Plug.Head 44 | plug Plug.Session, @session_options 45 | plug AppWeb.Router 46 | end 47 | -------------------------------------------------------------------------------- /lib/app_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.Router do 2 | use AppWeb, :router 3 | 4 | pipeline :browser do 5 | plug :accepts, ["html"] 6 | plug :fetch_session 7 | plug :fetch_live_flash 8 | plug :put_root_layout, html: {AppWeb.Layouts, :root} 9 | plug :protect_from_forgery 10 | plug :put_secure_browser_headers 11 | end 12 | 13 | scope "/", AppWeb do 14 | pipe_through :browser 15 | 16 | get "/", PageController, :home 17 | get "/auth/github/callback", GithubAuthController, :index 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/app_web/telemetry.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.Telemetry do 2 | use Supervisor 3 | import Telemetry.Metrics 4 | 5 | def start_link(arg) do 6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__) 7 | end 8 | 9 | @impl true 10 | def init(_arg) do 11 | children = [ 12 | # Telemetry poller will execute the given period measurements 13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics 14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} 15 | # Add reporters as children of your supervision tree. 16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} 17 | ] 18 | 19 | Supervisor.init(children, strategy: :one_for_one) 20 | end 21 | 22 | def metrics do 23 | [ 24 | # Phoenix Metrics 25 | summary("phoenix.endpoint.start.system_time", 26 | unit: {:native, :millisecond} 27 | ), 28 | summary("phoenix.endpoint.stop.duration", 29 | unit: {:native, :millisecond} 30 | ), 31 | summary("phoenix.router_dispatch.start.system_time", 32 | tags: [:route], 33 | unit: {:native, :millisecond} 34 | ), 35 | summary("phoenix.router_dispatch.exception.duration", 36 | tags: [:route], 37 | unit: {:native, :millisecond} 38 | ), 39 | summary("phoenix.router_dispatch.stop.duration", 40 | tags: [:route], 41 | unit: {:native, :millisecond} 42 | ), 43 | summary("phoenix.socket_connected.duration", 44 | unit: {:native, :millisecond} 45 | ), 46 | summary("phoenix.channel_joined.duration", 47 | unit: {:native, :millisecond} 48 | ), 49 | summary("phoenix.channel_handled_in.duration", 50 | tags: [:event], 51 | unit: {:native, :millisecond} 52 | ), 53 | 54 | # VM Metrics 55 | summary("vm.memory.total", unit: {:byte, :kilobyte}), 56 | summary("vm.total_run_queue_lengths.total"), 57 | summary("vm.total_run_queue_lengths.cpu"), 58 | summary("vm.total_run_queue_lengths.io") 59 | ] 60 | end 61 | 62 | defp periodic_measurements do 63 | [ 64 | # A module, function and arguments to be invoked periodically. 65 | # This function must call :telemetry.execute/3 and a metric must be added above. 66 | # {AppWeb, :count_users, []} 67 | ] 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule App.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :app, 7 | version: "1.7.10", 8 | elixir: "~> 1.14", 9 | elixirc_paths: elixirc_paths(Mix.env()), 10 | start_permanent: Mix.env() == :prod, 11 | aliases: aliases(), 12 | deps: deps(), 13 | test_coverage: [tool: ExCoveralls], 14 | preferred_cli_env: [ 15 | c: :test, 16 | coveralls: :test, 17 | "coveralls.json": :test, 18 | "coveralls.html": :test, 19 | t: :test 20 | ] 21 | ] 22 | end 23 | 24 | # Configuration for the OTP application. 25 | # 26 | # Type `mix help compile.app` for more information. 27 | def application do 28 | [ 29 | mod: {App.Application, []}, 30 | extra_applications: [:logger, :runtime_tools] 31 | ] 32 | end 33 | 34 | # Specifies which paths to compile per environment. 35 | defp elixirc_paths(:test), do: ["lib", "test/support"] 36 | defp elixirc_paths(_), do: ["lib"] 37 | 38 | # Specifies your project dependencies. 39 | # 40 | # Type `mix help deps` for examples and options. 41 | defp deps do 42 | [ 43 | {:phoenix, "~> 1.7.10"}, 44 | {:phoenix_html, "~> 4.0"}, 45 | {:phoenix_live_reload, "~> 1.2", only: :dev}, 46 | {:phoenix_live_view, "~> 1.0.0"}, 47 | {:floki, ">= 0.30.0", only: :test}, 48 | {:telemetry_metrics, "~> 1.0"}, 49 | {:telemetry_poller, "~> 1.0"}, 50 | {:jason, "~> 1.2"}, 51 | {:dns_cluster, "~> 0.2.0"}, 52 | {:plug_cowboy, "~> 2.5"}, 53 | 54 | # The star of the show: github.com/dwyl/elixir-auth-github 55 | {:elixir_auth_github, "~> 1.6.5"}, 56 | 57 | # Track test coverage: github.com/parroty/excoveralls 58 | {:excoveralls, "~> 0.15", only: [:test, :dev]} 59 | ] 60 | end 61 | 62 | # Aliases are shortcuts or tasks specific to the current project. 63 | # For example, to install project dependencies and perform other setup tasks, run: 64 | # 65 | # $ mix setup 66 | # 67 | # See the documentation for `Mix` for more info on aliases. 68 | defp aliases do 69 | [ 70 | test: ["test"], 71 | t: ["test"], 72 | c: ["coveralls.html"], 73 | s: ["phx.server"] 74 | ] 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"}, 3 | "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, 4 | "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"}, 5 | "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"}, 6 | "cowlib": {:hex, :cowlib, "2.15.0", "3c97a318a933962d1c12b96ab7c1d728267d2c523c25a5b57b0f93392b6e9e25", [:make, :rebar3], [], "hexpm", "4f00c879a64b4fe7c8fcb42a4281925e9ffdb928820b03c3ad325a617e857532"}, 7 | "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, 8 | "elixir_auth_github": {:hex, :elixir_auth_github, "1.6.6", "2f41f35c398c0e1c339d1e73018ae3dd6b19a443ce2b545aa1a59ac24034f93e", [:mix], [{:httpoison, "~> 2.1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "3a42577e609c6146d295fb9c728781083be989638ab4d868e09c7a1a32c5ff97"}, 9 | "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, 10 | "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, 11 | "floki": {:hex, :floki, "0.37.1", "d7aaee758c8a5b4a7495799a4260754fec5530d95b9c383c03b27359dea117cf", [:mix], [], "hexpm", "673d040cb594d31318d514590246b6dd587ed341d3b67e17c1c0eb8ce7ca6f04"}, 12 | "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.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.4.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", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, 13 | "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"}, 14 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, 15 | "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, 16 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, 17 | "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, 18 | "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, 19 | "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, 20 | "phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, 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]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"}, 21 | "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, 22 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.0", "2791fac0e2776b640192308cc90c0dbcf67843ad51387ed4ecae2038263d708d", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b3a1fa036d7eb2f956774eda7a7638cf5123f8f2175aca6d6420a7f95e598e1c"}, 23 | "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.14", "621f075577e286ff1e67d6de085ddf6f364f934d229c1c5564be1ef4c77908b9", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6dcb3f236044cd9d1c0d0996331bef72716b1991bbd8e0725a617c0d95a9483"}, 24 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, 25 | "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, 26 | "plug": {:hex, :plug, "1.18.0", "d78df36c41f7e798f2edf1f33e1727eae438e9dd5d809a9997c463a108244042", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "819f9e176d51e44dc38132e132fe0accaf6767eab7f0303431e404da8476cfa2"}, 27 | "plug_cowboy": {:hex, :plug_cowboy, "2.7.3", "1304d36752e8bdde213cea59ef424ca932910a91a07ef9f3874be709c4ddb94b", [: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", "77c95524b2aa5364b247fa17089029e73b951ebc1adeef429361eab0bb55819d"}, 28 | "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, 29 | "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, 30 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, 31 | "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, 32 | "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, 33 | "telemetry_poller": {:hex, :telemetry_poller, "1.2.0", "ba82e333215aed9dd2096f93bd1d13ae89d249f82760fcada0850ba33bac154b", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7216e21a6c326eb9aa44328028c34e9fd348fb53667ca837be59d0aa2a0156e8"}, 34 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, 35 | "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, 36 | "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, 37 | } 38 | -------------------------------------------------------------------------------- /priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | # Script for populating the database. You can run it as: 2 | # 3 | # mix run priv/repo/seeds.exs 4 | # 5 | # Inside the script, you can read and write to any of your 6 | # repositories directly: 7 | # 8 | # App.Repo.insert!(%App.SomeSchema{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /rel/overlays/bin/migrate: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd -P -- "$(dirname -- "$0")" 3 | exec ./app eval App.Release.migrate 4 | -------------------------------------------------------------------------------- /rel/overlays/bin/migrate.bat: -------------------------------------------------------------------------------- 1 | call "%~dp0\app" eval App.Release.migrate 2 | -------------------------------------------------------------------------------- /rel/overlays/bin/server: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd -P -- "$(dirname -- "$0")" 3 | PHX_SERVER=true exec ./app start 4 | -------------------------------------------------------------------------------- /rel/overlays/bin/server.bat: -------------------------------------------------------------------------------- 1 | set PHX_SERVER=true 2 | call "%~dp0\app" start 3 | -------------------------------------------------------------------------------- /test/app_web/controllers/error_html_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.ErrorHTMLTest do 2 | use AppWeb.ConnCase, async: true 3 | 4 | # Bring render_to_string/4 for testing custom views 5 | import Phoenix.Template 6 | 7 | test "renders 404.html" do 8 | assert render_to_string(AppWeb.ErrorHTML, "404", "html", []) == "Not Found" 9 | end 10 | 11 | test "renders 500.html" do 12 | assert render_to_string(AppWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/app_web/controllers/error_json_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.ErrorJSONTest do 2 | use AppWeb.ConnCase, async: true 3 | 4 | test "renders 404" do 5 | assert AppWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} 6 | end 7 | 8 | test "renders 500" do 9 | assert AppWeb.ErrorJSON.render("500.json", %{}) == 10 | %{errors: %{detail: "Internal Server Error"}} 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/app_web/controllers/github_auth_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.GithubAuthControllerTest do 2 | use AppWeb.ConnCase 3 | 4 | test "GET /auth/github/callback", %{conn: conn} do 5 | conn = get(conn, "/auth/github/callback", %{"code" => "123456"}) 6 | assert html_response(conn, 200) =~ "signed in" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/app_web/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.PageControllerTest do 2 | use AppWeb.ConnCase 3 | 4 | test "GET /", %{conn: conn} do 5 | conn = get(conn, ~p"/") 6 | assert html_response(conn, 200) =~ "Welcome to Awesome App!" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule AppWeb.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use AppWeb.ConnCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # The default endpoint for testing 23 | @endpoint AppWeb.Endpoint 24 | 25 | use AppWeb, :verified_routes 26 | 27 | # Import conveniences for testing with connections 28 | import Plug.Conn 29 | import Phoenix.ConnTest 30 | import AppWeb.ConnCase 31 | end 32 | end 33 | 34 | setup _tags do 35 | {:ok, conn: Phoenix.ConnTest.build_conn()} 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------