[
  {
    "path": ".coveralls.yml",
    "content": "service_name: travis-pro\nrepo_token: 2ZS2MYr7uA24NM8smvOIExISDZpNgS5VN"
  },
  {
    "path": ".credo.exs",
    "content": "# This file contains the configuration for Credo and you are probably reading\n# this after creating it with `mix credo.gen.config`.\n#\n# If you find anything wrong or unclear in this file, please report an\n# issue on GitHub: https://github.com/rrrene/credo/issues\n#\n%{\n  #\n  # You can have as many configs as you like in the `configs:` field.\n  configs: [\n    %{\n      #\n      # Run any exec using `mix credo -C <name>`. If no exec name is given\n      # \"default\" is used.\n      #\n      name: \"default\",\n      #\n      # These are the files included in the analysis:\n      files: %{\n        #\n        # You can give explicit globs or simply directories.\n        # In the latter case `**/*.{ex,exs}` will be used.\n        #\n        included: [\"lib/\", \"src/\", \"test/\", \"web/\", \"apps/\"],\n        excluded: [~r\"/_build/\", ~r\"/deps/\"]\n      },\n      #\n      # If you create your own checks, you must specify the source files for\n      # them here, so they can be loaded by Credo before running the analysis.\n      #\n      requires: [],\n      #\n      # If you want to enforce a style guide and need a more traditional linting\n      # experience, you can change `strict` to `true` below:\n      #\n      strict: false,\n      #\n      # If you want to use uncolored output by default, you can change `color`\n      # to `false` below:\n      #\n      color: true,\n      #\n      # You can customize the parameters of any check by adding a second element\n      # to the tuple.\n      #\n      # To disable a check put `false` as second element:\n      #\n      #     {Credo.Check.Design.DuplicatedCode, false}\n      #\n      checks: [\n        #\n        ## Consistency Checks\n        #\n        {Credo.Check.Consistency.ExceptionNames},\n        {Credo.Check.Consistency.LineEndings},\n        {Credo.Check.Consistency.ParameterPatternMatching},\n        {Credo.Check.Consistency.SpaceAroundOperators},\n        {Credo.Check.Consistency.SpaceInParentheses},\n        {Credo.Check.Consistency.TabsOrSpaces},\n\n        #\n        ## Design Checks\n        #\n        # You can customize the priority of any check\n        # Priority values are: `low, normal, high, higher`\n        #\n        {Credo.Check.Design.AliasUsage, false},\n        # For some checks, you can also set other parameters\n        #\n        # If you don't want the `setup` and `test` macro calls in ExUnit tests\n        # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just\n        # set the `excluded_macros` parameter to `[:schema, :setup, :test]`.\n        #\n        {Credo.Check.Design.DuplicatedCode, excluded_macros: []},\n        # You can also customize the exit_status of each check.\n        # If you don't want TODO comments to cause `mix credo` to fail, just\n        # set this value to 0 (zero).\n        #\n        {Credo.Check.Design.TagTODO, false},\n        {Credo.Check.Design.TagFIXME, false},\n\n        #\n        ## Readability Checks\n        #\n        {Credo.Check.Readability.AliasOrder, false},\n        {Credo.Check.Readability.FunctionNames},\n        {Credo.Check.Readability.LargeNumbers},\n        {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 180},\n        {Credo.Check.Readability.ModuleAttributeNames},\n        {Credo.Check.Readability.ModuleDoc},\n        {Credo.Check.Readability.ModuleNames},\n        {Credo.Check.Readability.ParenthesesOnZeroArityDefs},\n        {Credo.Check.Readability.ParenthesesInCondition},\n        {Credo.Check.Readability.PredicateFunctionNames},\n        {Credo.Check.Readability.PreferImplicitTry},\n        {Credo.Check.Readability.RedundantBlankLines},\n        {Credo.Check.Readability.StringSigils},\n        {Credo.Check.Readability.TrailingBlankLine},\n        {Credo.Check.Readability.TrailingWhiteSpace},\n        {Credo.Check.Readability.VariableNames},\n        {Credo.Check.Readability.Semicolons},\n        {Credo.Check.Readability.SpaceAfterCommas},\n\n        #\n        ## Refactoring Opportunities\n        #\n        {Credo.Check.Refactor.DoubleBooleanNegation, false},\n        {Credo.Check.Refactor.CondStatements},\n        {Credo.Check.Refactor.CyclomaticComplexity},\n        {Credo.Check.Refactor.FunctionArity},\n        {Credo.Check.Refactor.LongQuoteBlocks},\n        {Credo.Check.Refactor.MatchInCondition},\n        {Credo.Check.Refactor.NegatedConditionsInUnless},\n        {Credo.Check.Refactor.NegatedConditionsWithElse},\n        {Credo.Check.Refactor.Nesting},\n        {Credo.Check.Refactor.PipeChainStart,\n         excluded_argument_types: [:atom, :binary, :fn, :keyword], excluded_functions: []},\n        {Credo.Check.Refactor.UnlessWithElse},\n\n        #\n        ## Warnings\n        #\n        {Credo.Check.Warning.BoolOperationOnSameValues},\n        {Credo.Check.Warning.ExpensiveEmptyEnumCheck},\n        {Credo.Check.Warning.IExPry},\n        {Credo.Check.Warning.IoInspect},\n        {Credo.Check.Warning.LazyLogging},\n        {Credo.Check.Warning.OperationOnSameValues},\n        {Credo.Check.Warning.OperationWithConstantResult},\n        {Credo.Check.Warning.UnusedEnumOperation},\n        {Credo.Check.Warning.UnusedFileOperation},\n        {Credo.Check.Warning.UnusedKeywordOperation},\n        {Credo.Check.Warning.UnusedListOperation},\n        {Credo.Check.Warning.UnusedPathOperation},\n        {Credo.Check.Warning.UnusedRegexOperation},\n        {Credo.Check.Warning.UnusedStringOperation},\n        {Credo.Check.Warning.UnusedTupleOperation},\n        {Credo.Check.Warning.RaiseInsideRescue},\n\n        #\n        # Controversial and experimental checks (opt-in, just remove `, false`)\n        #\n        {Credo.Check.Refactor.ABCSize, false},\n        {Credo.Check.Refactor.AppendSingleItem, false},\n        {Credo.Check.Refactor.VariableRebinding, false},\n        {Credo.Check.Warning.MapGetUnsafePass, false},\n        {Credo.Check.Consistency.MultiAliasImportRequireUse, false},\n\n        #\n        # Deprecated checks (these will be deleted after a grace period)\n        #\n        {Credo.Check.Readability.Specs, false}\n\n        #\n        # Custom checks can be created using `mix credo.gen.check`.\n        #\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": ".formatter.exs",
    "content": "[\n  inputs: [\n    \"{lib,test,priv}/**/*.{ex,exs}\",\n    \"mix.exs\",\n    \".formatter.exs\",\n    \".iex.exs\",\n    \".credo.exs\"\n  ],\n  import_deps: [:ecto, :plug, :phoenix]\n]\n"
  },
  {
    "path": ".github/workflows/elixir.yml",
    "content": "name: Elixir CI\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    services:\n      postgres:\n        image: postgres\n        ports:\n          - 5432:5432\n        env:\n          POSTGRES_PASSWORD: postgres\n          POSTGRES_USER: postgres\n        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5\n    steps:\n    - uses: actions/checkout@v2\n    - name: Setup elixir\n      uses: actions/setup-elixir@v1\n      with:\n        elixir-version: 1.9.4 # Define the elixir version [required]\n        otp-version: 22.2 # Define the OTP version [required]\n    - name: Install Dependencies\n      run: mix deps.get\n    - name: Run Tests\n      run: mix test\n      env: \n        DB_HOST: postgres\n"
  },
  {
    "path": ".gitignore",
    "content": "# App artifacts\n/_build\n/db\n/deps\n/*.ez\n\n# Generated on crash by the VM\nerl_crash.dump\n\n# Generated on crash by NPM\nnpm-debug.log\n\n# Static artifacts\n/assets/node_modules\n\n# Since we are building assets from assets/,\n# we ignore priv/static. You may want to comment\n# this depending on your deployment strategy.\n/priv/static/\n\n# Files matching config/*.secret.exs pattern contain sensitive\n# data and you should not commit them into version control.\n#\n# Alternatively, you may comment the line below and commit the\n# secrets files as long as you replace their contents by environment\n# variables.\n/config/*.secret.exs\n/assets/yarn.lock\n/assets/package-lock.json\n\n# Ingore the whole configuration files from the .idea folder\n/.idea\n/.elixir_ls"
  },
  {
    "path": ".iex.exs",
    "content": "import Ecto.{Changeset, Query}\n\nalias Mipha.{\n  Repo,\n  Accounts,\n  Topics,\n  Replies,\n  Stars,\n  Collections,\n  Follows,\n  Markdown,\n  Mailer,\n  Notifications,\n  Utils,\n  Token\n}\n\nalias Accounts.{User, Location, Company, Team}\nalias Topics.{Node, Topic}\nalias Replies.Reply\nalias Stars.Star\nalias Collections.Collection\nalias Follows.Follow\nalias Notifications.{Notification, UserNotification}\nalias Utils.Store\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: elixir\nelixir:\n  - '1.8.1'\naddons:\n  postgresql: '9.4'\nservices:\n  - postgresql\ncache:\n  directories:\n    - _build\n    - deps\nenv:\n  - MIX_ENV=test\nbefore_script:\n  - mix ecto.create && mix ecto.migrate\nscript:\n  - mix test\n  - mix coveralls.travis\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018 Zven Wang\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# code helper\niex:\n\tiex --erl \"-kernel shell_history enabled\" -S mix\nserve:\n\tiex --erl \"-kernel shell_history enabled\" -S mix phx.server\ncredo:\n\tmix credo --strict\ncoveralls:\n\tmix coveralls\n\n# docker helper\nstart.pg:\n\tdocker-compose -f docker/docker-compose.yml up -d"
  },
  {
    "path": "Procfile",
    "content": "web: MIX_ENV=prod mix phx.server\n"
  },
  {
    "path": "README.ZH.md",
    "content": "# Mipha\n\n[![Build Status](https://travis-ci.org/zven21/mipha.svg?branch=master)](https://travis-ci.org/zven21/mipha)\n[![Coverage Status](https://coveralls.io/repos/github/zven21/mipha/badge.svg?branch=excoveralls)](https://coveralls.io/github/zven21/mipha?branch=excoveralls)\n\n[English](./README.md) | 简体中文\n\n## 目录\n\n* [简介](#简介)\n* [启动程序](#启动程序)\n* [数据库表关系](#数据库表关系)\n* [项目结构](#项目结构)\n* [进度与计划](#进度与计划)\n* [其他](#其他)\n\n## 简介\n\nMipha 是一个用 Elixir 模(chao)仿(xi) [RubyChina](https://ruby-china.org/) 的开源论坛。\n有兴趣参与开发的童鞋，可以加入 [Slack channel](https://elixir-mipha.slack.com/)\n\n## 启动程序\n\n```bash\n# clone 项目\ngit clone git@github.com:zven21/mipha.git\n# 初始化，如果是 Mac 电脑，可以执行 ./script/setup，\ncd mipha && ./script/setup\n# 数据库初始化，在 config/dev.exs 内配置开发环境的 postgres 账号和密码\nmix ecto.reset\n# 启动项目 :-)\nmix phx.serve\n```\n\n如果要使用上传图片、发送邮件或第三方 Github 登录功能，需要配置环境变量配置在  `~/.profile` 或 `~/.zshrc` 中\n\n```bash\n# 七牛图片服务器的账号和密码\nexport QINIU_ACCESS_KEY\nexport QINIU_SECRET_KEY\n# 发送邮件服务器的账号和密码\nexport GMAIL_USERNAME\nexport GMAIL_PASSWORD\n# Github 第三方认证登录的 Key 与 Secret\nexport GITHUB_CLIENT_ID\nexport GITHUB_CLIENT_SECRET\n```\n\n## 数据库表关系\n\n![ ](https://l.ruby-china.com/photo/2018/b96739ac-94d4-433e-9693-de528466c6d3.jpeg!large)\n\n## 项目结构\n\n目前的项目结构设计很直接，不属于 web 部分的功能，都放到了 lib/mipha 内，后续会根据业务需求调整。\n\n```bash\n.\n├── assets                     # JS CSS 与静态资源\n├── lib                        # 项目 elixir 代码\n│   ├── mipha                  # 目前的逻辑是非 Web 部分的代码放到这里。\n│   │   ├── accounts           # 社区用户、团队、公司、地址\n│   │   ├── collections        # 收藏\n│   │   ├── follows            # 关注\n│   │   ├── markdown           # markdown 文本解析相关策略\n│   │   ├── notifications      # 站内信（通知）\n│   │   ├── replies            # 帖子评论\n│   │   ├── stars              # 点赞，目前支持点赞帖子与评论\n│   │   ├── topics             # 帖子与帖子的分类（节点)\n│   │   ├── utils              # 工具库\n│   │   ├── mailer.ex          # 发送邮件\n│   │   ├── markdown.ex        # markdown 文本解析\n│   │   ├── qiniu.ex           # 七牛上传图片\n│   │   ├── regexp.ex          # 正则表达式\n│   │   ├── token.ex           # token 验证\n│   ├── mipha_web\n│   │   ├── channels           # socket WS 协议相关代码\n│   │   ├── controllers        # Controllers\n│   │   │   ├── admin          # admin 管理台\n│   │   ├── plugs              # Plugs\n│   │   ├── templates          # Templates\n│   │   ├── views              # Views\n│   │   ├── email.ex           # 发送邮件方法及调用邮件模板\n│   │   ├── session.ex         # 用户登录相关的 session 处理\n│   ├── mipha.ex\n│   ├── mipha_web.ex\n└── test                       # 测试\n\n```\n\n## 进度与计划\n\n目前在[第一迭代](https://github.com/zven21/mipha/milestone/1)，按照 RubyChina 的功能实现。欢迎提 Issue 或 PR。\n\n## 其他\n\n* [[开源项目] 用 Elixir 撸了一个 RubyChina](https://ruby-china.org/topics/37158)\n"
  },
  {
    "path": "README.md",
    "content": "# Mipha\n\n[![Build Status](https://travis-ci.org/zven21/mipha.svg?branch=master)](https://travis-ci.org/zven21/mipha)\n[![Coverage Status](https://coveralls.io/repos/github/zven21/mipha/badge.svg?branch=excoveralls)](https://coveralls.io/github/zven21/mipha?branch=excoveralls)\n\n\nEnglish | [简体中文](./README.ZH.md)\n\n## Table of contents\n\n* [Introduction](#introduction)\n* [Getting started](#getting-started)\n* [Database relationship](#database-relationship)\n* [Project structure](#project-structure)\n* [Make a pull request](#make-a-pull-request)\n* [License](#license)\n\n## Introduction\n\nMipha is an open-source elixir forum build with phoenix 1.4 (inspire by [homeland](https://ruby-china.org)).\n\n## Getting started\n\n```bash\n# clone\ngit clone git@github.com:zven21/mipha.git\n# init setup\ncd mipha && ./script/setup\n# db create && db migrate db seeds\nmix ecto.reset\n# run\nmix phx.serve\n```\n\n## Database relationship\n\n![ ](https://l.ruby-china.com/photo/2018/b96739ac-94d4-433e-9693-de528466c6d3.jpeg!large)\n\n## Project structure\n\n```bash\n.\n├── assets                     # JS CSS and static file.\n├── lib                        #\n│   ├── mipha                  #\n│   │   ├── accounts           # user team company location model.\n│   │   ├── collections        # user collection.\n│   │   ├── follows            # follows\n│   │   ├── markdown           # markdown\n│   │   ├── notifications      # notification\n│   │   ├── replies            # the reply of topic.\n│   │   ├── stars              # like topic or reply\n│   │   ├── topics             # topic and node.\n│   │   ├── utils              #\n│   │   ├── mailer.ex          # send email.\n│   │   ├── markdown.ex        #\n│   │   ├── qiniu.ex           # image upload\n│   │   ├── regexp.ex          # regex\n│   │   ├── token.ex           # token verification, reset password etc.\n│   ├── mipha_web\n│   │   ├── channels           # socket\n│   │   ├── controllers        # Controllers\n│   │   │   ├── admin          # admin dashboard\n│   │   ├── plugs              # Plugs\n│   │   ├── templates          # Templates\n│   │   ├── views              # Views\n│   │   ├── email.ex           #\n│   │   ├── session.ex         #\n│   ├── mipha.ex\n│   ├── mipha_web.ex\n└── test                       # test\n```\n\n## Contributing\n\nBug report or pull request are welcome.\n\n## Make a pull request\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\nPlease write unit test with your code if necessary.\n\n## License\n\nThe proj is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT)."
  },
  {
    "path": "assets/.eslintrc.json",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"extends\": [\"prettier\"],\n  \"rules\": {\n    \"indent\": 0\n  }\n}\n"
  },
  {
    "path": "assets/.yarnrc",
    "content": "--ignore-engines true"
  },
  {
    "path": "assets/css/admin.scss",
    "content": "$fa-font-path: \"~font-awesome/fonts\";\n@import \"~font-awesome/scss/font-awesome.scss\";\n@import \"~bootstrap/scss/bootstrap\";\n@import \"./common/variables/mipha\";\n@import \"./common/components/header\";\n\nbody {\n  background-color: #e5e5e5;\n}\n\n@media (min-width: 1200px) {\n  .container {\n    max-width: 1480px;\n  }\n}\n\n.card {\n  margin-top: 10px;\n  margin-bottom: 5px;\n}\n\nbody,\np,\nol,\nul,\ntd,\na,\nspan {\n  font-size: 14px;\n  line-height: 18px;\n}\n\nbody {\n  color: #333;\n}\n\nbody,\np,\nol,\nul,\ntd {\n  font-family: verdana, arial, helvetica, sans-serif;\n  font-size: 13px;\n  line-height: 18px;\n}\n\n.header {\n  .navbar-nav-scroll {\n    .navbar-nav {\n      width: 100%;\n      height: 50px;\n      overflow-x: scroll;\n    }\n  }\n  #navbar-new-menu,\n  .notification-count {\n    display: none;\n  }\n  .user-bar {\n    background: #FFF;\n    min-width: auto;\n  }\n}\n\n.nav-item .nav-link {\n  font-size: 14px;\n  padding-left: 10px !important;\n  padding-right: 10px !important;\n  &.active {\n    color: $grape;\n  }\n}\n\n.navbar-search {\n  display: none !important;\n}\n\n.card-body h1,\nfieldset legend {\n  border: 0px;\n  font-size: 18px;\n  margin-top: 0px;\n  margin-bottom: 20px;\n  padding: 0;\n  line-height: 100%;\n}\n\npre {\n  background-color: #eee;\n  padding: 10px;\n  font-size: 11px;\n}\n\n.group {\n  margin-bottom: 20px;\n}\n\n.group h2 {\n  font-size: 12px;\n  margin-bottom: 8px;\n}\n\n.group ul {\n  margin: 0 20px;\n}\n\n.pagination {\n  clear: both;\n  margin-top: 10px;\n  text-align: left;\n}\n\n.toolbar {\n  margin-bottom: 15px;\n}\n\ntable tr.deleted td {\n  text-decoration: line-through;\n  color: #999\n}\n\ntable tr td a.fa {\n  text-decoration: normal;\n  color: #666;\n  margin-right: 4px;\n  &:hover {\n    color: #333;\n    text-decoration: none;\n  }\n}\n\n.nav>li>a {\n  padding: 14px 6px;\n}\n\n.stat {\n  margin: 15px;\n  padding-bottom: 15px;\n  border-bottom: 1px dashed #eee;\n  .total {\n    font-size: 18px;\n    color: #555;\n    margin-bottom: 10px;\n    margin-top: 8px;\n  }\n  .name {\n    font-size: 14px;\n    color: #999;\n  }\n  .total-week {\n    color: $grape;\n    font-size: 12px;\n    margin: 0 10px;\n  }\n  .total-month {\n    color: $green;\n    font-size: 12px;\n  }\n}"
  },
  {
    "path": "assets/css/app/components/_footer.scss",
    "content": "footer {\n  margin-top: 10px;\n  margin-bottom: 20px;\n  color: #909090;\n  a {\n    color: #666;\n  }\n  .links {\n    color: #ddd;\n  }\n  .socials {\n    a {\n      font-size: 20px;\n      margin-right: 8px;\n    }\n  }\n}"
  },
  {
    "path": "assets/css/app/components/_notification.scss",
    "content": ".notifications {\n  .card-header {\n    font-size: 16px;\n    line-height: 32px;\n  }\n  .card-body {\n    padding-top: 0;\n    padding-bottom: 0;\n  }\n  .notification-group {\n    padding: 10px 0;\n    .group-title {\n      color: #aaa;\n      border-bottom: 1px solid #eee;\n      padding: 5px 0;\n    }\n  }\n  .notification {\n    margin: 0 -15px;\n    padding: 10px 15px;\n    &:last-child {\n      border-bottom: 0;\n    }\n    a {\n      color: #555;\n    }\n    &.unread {\n      color: #444;\n      .media-heading {\n        font-weight: bold;\n      }\n      a {\n        color: #222;\n        text-decoration: underline;\n      }\n    }\n    .media-content {\n      color: #444;\n      a {\n        color: #999 !important;\n      }\n      p {\n        font-size: 14px;\n        margin-bottom: 6px;\n      }\n      p:last-child {\n        margin-bottom: 0;\n      }\n    }\n    .user-avatar {\n      img {\n        width: 32px;\n        height: 32px;\n        border-radius: 120px;\n      }\n    }\n    .media-right {\n      min-width: 40px;\n      color: #AAA;\n      font-size: 13px;\n    }\n  }\n}"
  },
  {
    "path": "assets/css/app/components/base.scss",
    "content": "@mixin clearfix() {\n  &:before,\n  &:after {\n    content: \" \";\n    display: table;\n  }\n  &:after {\n    clear: both;\n  }\n  & {\n    zoom: 1;\n  }\n}\n\n/* 去掉 Bootstrap 的蓝色 outline */\n\n* {\n  outline-style: none;\n}\n\ntextarea,\ninput[text] {\n  outline: none !important;\n  box-shadow: none !important;\n  -webkit-appearance: none !important;\n}\n\ntextarea,\n#preview {\n  overflow: scroll;\n  &::-webkit-scrollbar {\n    width: 12px;\n  }\n  &::-webkit-scrollbar-thumb {\n    background: #f0f0f0;\n    border: 3px solid #fff;\n    border-radius: 9px;\n  }\n}\n\n.alert {\n  box-shadow: 0 0 0;\n  padding: 0.65rem 1.25rem;\n  border: 2px solid transparent !important;\n  .close {\n    color: $primary;\n    opacity: .7;\n  }\n}\n\n.alert-block {\n  ul {\n    margin-bottom: 0;\n    margin-top: 1.25em;\n  }\n}\n\n.alert-danger {\n  color: $red;\n  background: lighten($red, 43%);\n}\n\n.alert-warning {\n  color: darken($yellow, 10%);\n  background: lighten($yellow, 45%);\n}\n\n.alert-success {\n  color: $green;\n  background: lighten($green, 65%);\n}\n\n.alert-info {\n  color: $blue;\n  background: lighten($blue, 45%);\n}\n\n.excaptcha-image {\n  width: 120px;\n}\n\n/* Bootstrap Theme */\n\nbody {\n  background: #e5e5e5;\n  color: $primary;\n  font-family: Helvetica, Arial, \"PingFang SC\", \"Noto Sans\", Roboto, \"Microsoft Yahei\", sans-serif;\n  font-size: 14px;\n  letter-spacing: .03em;\n}\n\n#main {\n  margin-top: 20px;\n}\n\n.sub-navbar {\n  background: #f9f9f9;\n  box-shadow: 0 1px 0px rgba(0, 0, 0, 0.02);\n  a {\n    color: $blue;\n  }\n}\n\n.btn-default {\n  border-color: $input-border-color;\n  background: #fcfcfc;\n  color: $primary !important;\n  &:hover {\n    border-color: #ccc;\n  }\n  &:active,\n  &.active {\n    color: #aaa;\n    border-color: $default;\n    background: #f0f0f0;\n  }\n}\n\n.page-topics {\n  .navbar-fixed-active {\n    .navbar-topic-title {\n      display: block;\n    }\n    .navbar-topic-title:after {\n      display: block;\n    }\n  }\n}\n\n.list-group-item {\n  a {\n    color: $primary;\n  }\n}\n\n.sidebar.col-md-3 {\n  padding-left: 0;\n}\n\n.nav-tabs {\n  li:first-child {\n    margin-left: 15px;\n  }\n  .nav-item {\n    .nav-link {\n      &:hover {\n        background: #f2f2f2;\n        border-color: #f2f2f2;\n      }\n    }\n  }\n}\n\n.close {\n  font-size: 1.2rem;\n  font-weight: normal;\n}\n\n.pagination {\n  margin: 0;\n  &>li:first-child>a,\n  &>li:first-child>span {\n    border-top-left-radius: 3px;\n    border-bottom-left-radius: 3px;\n  }\n  &>li:last-child>a,\n  &>li:last-child>span {\n    border-top-right-radius: 3px;\n    border-bottom-right-radius: 3px;\n  }\n  li>a {\n    color: $primary;\n  }\n  li>a,\n  .disabled>a,\n  li>span {\n    border-color: #E0E0E0 !important;\n  }\n  li>a:hover {\n    color: #555;\n    background: $gray;\n  }\n  li.active>a,\n  li.active>a:hover {\n    background-color: $blue !important;\n    border-color: $blue !important;\n    color: #FFF !important;\n  }\n}\n\n.pager {\n  margin: 0px;\n  .info {\n    line-height: 32px;\n    color: #ccc;\n    samp {\n      color: #999;\n    }\n  }\n  li>a,\n  li>span {\n    color: #666;\n    border-radius: 3px;\n    border: 0px;\n    background: transparent;\n    &:hover {\n      background: #fff;\n    }\n  }\n  li.disabled>a,\n  li.disabled>span {\n    color: #ddd;\n    background: transparent;\n    &:hover {\n      color: #ddd;\n      background: transparent;\n    }\n  }\n}\n\nabbr {\n  text-decoration: none;\n  border-bottom: 0px;\n  cursor: pointer;\n}\n\nabbr[title] {\n  border: 0 !important;\n  text-decoration: none;\n  cursor: text;\n}\n\na abbr[title] {\n  cursor: pointer;\n}\n\nkbd {\n  background-color: #f5f5f5;\n  color: #999;\n  border-radius: 2px;\n  border-color: #fafafa;\n  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.05);\n}\n\n.table>tbody>tr>td,\n.table>tbody>tr>th,\n.table>tfoot>tr>td,\n.table>tfoot>tr>th,\n.table>thead>tr>td,\n.table>thead>tr>th {\n  padding: 4px 5px;\n}\n\n.card {\n  margin-bottom: 15px;\n  border-top: 0;\n  box-shadow: 0 0 0;\n  border-color: #e5e6e9 #dfe0e4 #d0d1d5;\n  .card-header,\n  .card-footer {\n    padding: 6px 15px;\n  }\n  .card-body,\n  .list-group-item {\n    padding: 10px 15px;\n  }\n  .card-header {\n    background: #fafafa;\n    border-bottom-color: #eee;\n    color: #777;\n  }\n  .card-footer {\n    border-top-color: #eee;\n    background: #fafafa;\n  }\n}\n\n.nav-link {\n  color: $primary;\n  &:hover {\n    color: $primary;\n  }\n}\n\n.nav-stacked.nav-pills>li>a {\n  border-radius: 0px;\n  i.fa {\n    width: 20px;\n  }\n}\n\n/* Modal */\n\n.modal {\n  top: 50px;\n  .modal-content {\n    border: 0px;\n    box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.15), 0px 0px 1px 1px rgba(0, 0, 0, 0.05);\n    p {\n      margin-bottom: 5px;\n    }\n  }\n  .modal-header {\n    padding: 8px 15px;\n    border-bottom: 0px;\n    .modal-title {\n      font-size: 16px;\n      font-weight: bold;\n    }\n    .close {\n      font-size: 1.2rem;\n      font-weight: normal;\n    }\n  }\n  .modal-footer {\n    border-color: #e5e5e5;\n  }\n  @media (min-width: 768px) {\n    .modal-dialog {\n      width: 768px;\n    }\n  }\n}\n\n.modal-backdrop.in {\n  opacity: 1;\n  background-color: rgba(0, 0, 0, 0.1);\n}\n\n.list-group {\n  .list-group-item {\n    border-color: #eee;\n  }\n}\n\n.fa-spin {\n  -webkit-animation: fa-spin 0.8s infinite linear;\n  animation: fa-spin 0.8s infinite linear;\n}\n\n/* App Style */\n\n.opts {\n  color: #666;\n  a {\n    display: inline-block;\n    border-radius: 3px;\n  }\n  a:link,\n  a:visited {\n    color: $primary;\n    line-height: 100%;\n    padding: 4px;\n    text-decoration: none;\n  }\n  a:hover {\n    background: #eee;\n    text-decoration: none;\n  }\n}\n\n.turbolinks-progress-bar {\n  background-color: $blue !important;\n  height: 2px !important;\n}\n\n.pull-right.opts {\n  a {\n    margin-left: 5px;\n    margin-right: 0px;\n  }\n}\n\n.avatar {\n  .uface,\n  .media-object {\n    border-radius: 120px;\n  }\n}\n\n.avatar-16 {\n  width: 16px;\n  height: 16px;\n  border-radius: 120px;\n}\n\n.avatar-32 {\n  width: 28px;\n  height: 28px;\n  border-radius: 120px;\n}\n\n.avatar-48 {\n  width: 40px;\n  height: 40px;\n  border-radius: 120px;\n}\n\n.avatar-96 {\n  width: 96px;\n  height: 96px;\n  border-radius: 120px;\n}\n\n@media (max-width: 480px) {\n  .avatar-48 {\n    width: 28px;\n    height: 28px;\n  }\n}\n\n.uname {\n  color: #666;\n}\n\nimg.emoji {\n  width: 20px;\n  height: 20px;\n}\n\n.node-name {\n  background: #f0f0f0;\n  padding: 1px 3px;\n  color: #777;\n  margin-right: 5px;\n  &:hover {\n    color: #555;\n    text-decoration: none;\n    background: #e0e0e0;\n  }\n}\n\n.fa.awesome {\n  font-size: 13px;\n  color: $red;\n}\n\n.notification-count {\n  .count {\n    margin-left: 4px;\n    display: none;\n    font-size: 12px;\n  }\n  .new {\n    color: $red !important;\n    .count {\n      display: inline;\n      line-height: 100%;\n    }\n  }\n}\n\n.deleted {\n  text-decoration: line-through;\n  color: #e0e0e0;\n}\n\n.no-result {\n  color: #aaa;\n  padding-bottom: 20px;\n  text-align: center;\n}\n\n.opts a.active {\n  .fa {\n    @extend .animated;\n    @extend .bounceIn;\n    color: $red;\n  }\n}\n\n.setting-menu {\n  padding-right: 0;\n}\n\n.editor-toolbar {\n  margin-bottom: 5px;\n  .opts {\n    a.nav-link {\n      border: 1px solid $input-border-color;\n      @extend .btn-default;\n      font-size: 14px;\n      line-height: 14px;\n      display: inline-block;\n      padding: 8px;\n      margin: 0;\n    }\n  }\n  .reply-to {\n    margin-top: 10px;\n    font-size: 14px;\n    line-height: 14px;\n    .fa {\n      margin-right: 8px;\n    }\n    .close {\n      font-size: 14px;\n      margin-left: 5px;\n    }\n  }\n}\n\n@media (max-width: 480px) {\n  body {\n    font-size: 14px;\n    line-height: 100%;\n  }\n  .container {\n    padding: 0;\n  }\n  .card {\n    border-radius: 0;\n    border-left: 0px;\n    border-right: 0px;\n  }\n  #main {\n    margin-top: 1px;\n  }\n  .opts {\n    a {\n      padding: 5px !important;\n      margin: 0 !important;\n    }\n  }\n  .row {\n    margin: 0;\n    .col-md-9,\n    .col-md-8,\n    .col-md-6,\n    .col-md-5,\n    .col-md-4,\n    .col-md-3 {\n      padding: 0;\n    }\n  }\n  .sidebar {\n    display: none;\n  }\n  .hidden-mobile {\n    display: none !important;\n  }\n  .topics {\n    .topic {\n      padding: 8px !important;\n      margin: 0 -8px !important;\n      .avatar {\n        margin-top: 8px;\n      }\n      .count {\n        font-size: 12px;\n        width: 38px !important;\n      }\n    }\n  }\n  .pagination {\n    display: block;\n    li {\n      display: none;\n    }\n    li.prev,\n    li.next {\n      float: left;\n      display: block;\n      a {\n        border-radius: 20px !important;\n      }\n    }\n    li.next {\n      float: right;\n    }\n  }\n}\n\n.node-list {\n  .node {\n    margin-top: 0px;\n    &:last-child {\n      margin-bottom: 0px;\n    }\n    .media-left {\n      min-width: 120px;\n    }\n    label {\n      font-weight: normal;\n      color: #aaa;\n      text-align: right;\n    }\n    .name {\n      margin-bottom: 10px;\n      width: 100px;\n      display: block;\n      float: left;\n      text-align: left;\n      a:link,\n      a:visited {\n        color: #333;\n      }\n    }\n  }\n}\n\n.navbar {\n  &.fixed-title {\n    .navbar-topic-title {\n      display: none;\n    }\n  }\n  .navbar-topic-title {\n    display: none;\n  }\n}\n\n@media (min-width: 767px) {\n  .navbar {\n    &.fixed-title {\n      .navbar-topic-title {\n        display: block;\n      }\n      #main-nav-menu {\n        display: none;\n      }\n      .nav-search {\n        display: none;\n      }\n    }\n    .navbar-topic-title {\n      display: none;\n      height: 50px;\n      text-align: left;\n      overflow: hidden;\n      top: 0;\n      position: absolute;\n      background: #FFF;\n      display: none;\n      min-width: 400px;\n      &:after {\n        clear: all;\n        display: block;\n      }\n      a.topic-title {\n        display: inline;\n        text-decoration: none;\n        overflow: hidden;\n        line-height: 0;\n        max-width: 0px;\n        color: #000;\n        &:hover,\n        &:active,\n        &:visited {\n          color: #000;\n        }\n        i.fa {\n          color: #999;\n          margin-left: 3px;\n        }\n        i.fa-diamond {\n          color: $red;\n        }\n        i.fa-check {\n          color: $green;\n        }\n      }\n      .node {\n        line-height: 50px;\n        margin-left: 16px;\n        color: #777;\n        margin-right: 3px;\n      }\n      h1 {\n        margin: 0;\n        padding: 0;\n        font-size: 16px;\n        line-height: 50px;\n      }\n    }\n  }\n}\n\n@media (min-width: 992px) {\n  .navbar {\n    .navbar-topic-title {\n      a.topic-title {\n        max-width: 450px;\n      }\n    }\n  }\n}\n\n@media (min-width: 1200px) {\n  .navbar {\n    .navbar-topic-title {\n      a.topic-title {\n        max-width: 640px;\n      }\n    }\n  }\n}\n\n.move-page-buttons {\n  position: fixed;\n  bottom: 10px;\n  right: 10px;\n  width: 45px;\n  .btn {\n    background: #fff;\n  }\n}\n\n.node-header {\n  .container {\n    padding: 5px 30px;\n  }\n  @media (max-width: 480px) {\n    .container {\n      padding: 5px 8px;\n    }\n    .filter {\n      .all-nodes {\n        display: none;\n      }\n    }\n  }\n  .title {\n    font-size: 24px;\n    color: #333;\n    margin-bottom: 8px;\n    .total {\n      color: #999;\n      font-size: 14px;\n      margin-left: 10px;\n    }\n  }\n  .summary {\n    p:last-child {\n      margin-bottom: 0;\n    }\n  }\n  .filter {\n    &>li {\n      margin-right: 0px;\n      &.active {\n        a:link,\n        a:visited,\n        a:hover {\n          color: #000;\n          text-decoration: none;\n          border-bottom: 2px dotted #666666;\n        }\n      }\n      &>a {\n        background: transparent !important;\n        border-radius: 0px;\n        line-height: 100%;\n        padding: 8px 8px;\n        margin-right: 5px;\n        font-size: 14px;\n        border-bottom: 2px dotted transparent;\n        text-decoration: none;\n        display: inline-block;\n        color: #606060;\n        &.active {\n          color: #000;\n          text-decoration: none;\n          border-bottom: 2px dotted #666666;\n        }\n        &:hover {\n          border-color: #eee;\n          background: transparent;\n        }\n        &.all-nodes {\n          border-radius: 3px;\n          outline: 0 !important;\n          margin-right: 1.25rem;\n          background: #f0f0f0;\n          border: 0;\n          .caret-right {\n            display: inline-block;\n            width: 0;\n            height: 0;\n            margin-left: 10px;\n            vertical-align: middle;\n            border-left: 4px solid;\n            border-top: 4px solid transparent;\n            border-bottom: 4px solid transparent;\n          }\n          &:hover {\n            border: 0;\n            color: #222;\n          }\n        }\n      }\n    }\n  }\n  .prefix-filter {\n    @extend .filter;\n    &>li {\n      margin-right: 40px;\n      &>a {\n        padding: 0px 0px;\n      }\n    }\n  }\n}\n\n.topics-node {\n  .node {\n    display: none;\n  }\n}\n\n.topics {\n  .no-result {\n    padding-bottom: 0;\n    margin-bottom: 0;\n    padding: 100px 0;\n  }\n  .topics-group:first-child {\n    @media (max-width: 991px) {\n      .topic:last-child {\n        border-bottom: 1px solid #F0F0F0;\n      }\n    }\n  }\n  .topic {\n    min-height: 68px;\n    border-bottom: 1px solid #F0F0F0;\n    padding: 10px 15px;\n    margin: 0 -15px;\n    vertical-align: top;\n    &:first-child {\n      padding-top: 0;\n    }\n    &:last-child {\n      border-bottom: 0px;\n    }\n    .avatar {\n      padding-top: 6px;\n      margin-right: 10px;\n    }\n    .title {\n      font-size: 15px;\n      margin-bottom: 0;\n      a:link,\n      a:visited {\n        color: #222;\n        font-weight: 400;\n        line-height: 30px;\n        word-break: break-all;\n        .node {\n          color: #777;\n          margin-right: 3px;\n        }\n      }\n      a:active,\n      a:hover {\n        color: #555;\n        .node {\n          color: #555;\n        }\n        text-decoration: none;\n      }\n      i.fa {\n        color: #999;\n        margin-left: 3px;\n      }\n      i.fa-diamond {\n        color: $red;\n      }\n      i.fa-check {\n        color: $green;\n      }\n    }\n    .info {\n      color: #adaaa8;\n      font-size: 13px;\n      margin-top: 0;\n      a {\n        color: #797776;\n        text-decoration: underline;\n      }\n    }\n    .count {\n      width: 100px;\n      text-align: right;\n      padding-top: 1.25rem;\n      a:link,\n      a:hover,\n      a:visited {\n        line-height: 11px;\n        color: #fff;\n        font-size: 13px;\n        min-width: 32px;\n        text-align: center;\n        border-radius: 80px;\n        padding: 3px 8px 3px 8px;\n        display: inline-block;\n        text-decoration: none;\n      }\n      a:link {\n        background: rgba(79, 147, 248, 0.24);\n      }\n      a:hover {\n        background: #f0f0f0;\n      }\n      a.state-true,\n      a:visited {\n        background: #f0f0f0;\n      }\n    }\n  }\n}\n\n.topic-detail {\n  margin-bottom: 15px;\n  .card-body {\n    padding-top: 20px;\n    padding-bottom: 20px;\n  }\n  .card-header {\n    padding: 15px;\n    transition: all .3s;\n    h1 {\n      margin-top: 0;\n      font-size: 20px;\n      color: #333;\n      text-align: left;\n      line-height: 150%;\n      margin-bottom: 8px;\n      .node {\n        color: #777;\n        margin-right: 3px;\n      }\n      i.fa-check {\n        color: $green;\n        font-size: 16px;\n      }\n    }\n    .avatar {\n      text-align: right;\n    }\n  }\n  .label-awesome {\n    font-size: 16px;\n    background: lighten($red, 45%);\n    border-bottom: 1px solid lighten($red, 42%);\n    padding: 10px 15px;\n    color: $red;\n    .fa {\n      font-size: 16px;\n    }\n    a {\n      color: #aAa5a4;\n    }\n  }\n  .info {\n    color: #adaaa8;\n    font-size: 13px;\n    a {\n      color: $primary;\n    }\n    .node {\n      color: $primary;\n      font-weight: bold;\n    }\n    .user-name {\n      color: $primary;\n      font-size: 14px;\n    }\n    .team-name {\n      font-size: 14px;\n      color: $green;\n    }\n    em {\n      font-style: normal;\n    }\n    .opts {\n      a {\n        margin-left: 5px;\n        color: $primary;\n      }\n    }\n  }\n}\n\n#topic-sidebar {\n  position: fixed;\n  display: none;\n  width: 260px;\n  @media (min-width: 960px) {\n    display: block;\n    width: 242px;\n  }\n  @media (min-width: 1200px) {\n    display: block;\n    width: 292px;\n  }\n  .group {\n    text-align: center;\n    margin-bottom: 20px;\n  }\n  .buttons {\n    margin-top: 20px;\n    .likes {\n      a {\n        display: block;\n        width: 90px;\n        margin: 0 auto;\n        border-radius: 5px;\n        padding: 10px 0;\n      }\n      a:link,\n      a:hover,\n      a:visited {\n        text-decoration: none;\n        color: $primary;\n      }\n      a:hover {\n        background: rgba(0, 0, 0, 0.03);\n      }\n      i.fa {\n        display: block;\n        font-size: 40px;\n        color: $primary;\n      }\n      a.active {\n        i.fa {\n          color: $red;\n        }\n      }\n      span {\n        display: block;\n        color: #666;\n      }\n    }\n  }\n  .reply-buttons {\n    text-align: center;\n    .total {\n      margin-bottom: 10px;\n    }\n  }\n  a.btn-move-page {\n    color: $primary;\n  }\n}\n\n#replies {\n  margin-bottom: 15px;\n  .card-body {\n    padding-top: 0px;\n    padding-bottom: 0px;\n  }\n  .info {\n    .uname {\n      color: $primary;\n    }\n    .opts {\n      a {\n        font-size: 14px;\n        margin-left: 5px;\n        color: $primary;\n      }\n      a.edit {\n        display: none;\n      }\n    }\n  }\n  .reply {\n    margin: 0 -15px;\n    padding: 15px;\n    position: relative;\n    border-bottom: 1px solid #eee;\n    padding-left: 65px;\n    &.reply-system,\n    &.reply-deleted {\n      padding: 10px 15px;\n      font-size: 15px;\n      color: #666;\n      border-bottom: 1px solid #F0F0F0;\n      img.media-object {\n        border-radius: 180px;\n        display: inline-block;\n        margin-right: 3px;\n        vertical-align: text-bottom;\n      }\n      .time {\n        margin-left: 4px;\n        color: #aaa;\n      }\n      .ban-reason {\n        color: #444;\n        border-bottom: 1px dashed #eee;\n      }\n    }\n    .infos {\n      min-height: 48px;\n    }\n    .avatar {\n      position: absolute;\n      top: 17px;\n      left: 15px;\n    }\n    &:last-child {\n      border-bottom: 0px;\n    }\n    &.none {\n      text-align: center;\n      color: #999;\n      min-height: 32px;\n    }\n    &.light {\n      background: #F7F2FC;\n    }\n    &.popular {\n      background: #fffce9;\n    }\n    .info {\n      .name {\n        font-size: 14px;\n        a {\n          color: $primary;\n        }\n      }\n      color: #999;\n      margin-bottom: 10px;\n      font-size: 13px;\n      .floor {\n        color: #7AA87A;\n      }\n      a.time {\n        color: #999;\n        text-decoration: none !important;\n        cursor: pointer;\n        &:hover {\n          border-bottom: 1px dashed #ccc;\n        }\n      }\n    }\n    .opts {\n      a {\n        display: inline-block;\n        vertical-align: baseline;\n        line-height: 22px;\n        padding: 2px 5px;\n        height: 22px;\n        min-width: 22px;\n        text-align: center;\n      }\n    }\n    .reply-to-block {\n      padding: 10px 1.25rem;\n      background: #f7f7f7;\n      border-radius: 3px;\n      margin-bottom: 1.25rem;\n      .info {\n        margin: 0;\n        a {\n          color: $primary;\n        }\n        .media-object {\n          display: inline-block;\n          margin-right: 5px;\n          vertical-align: middle;\n        }\n      }\n      .markdown {\n        margin-top: 10px;\n        font-size: 15px;\n        p {\n          font-size: 15px;\n        }\n      }\n    }\n    .markdown {\n      pre {\n        margin-right: 0px;\n        margin-left: 0px;\n      }\n    }\n    @media (min-width: 1026px) {\n      .hideable {\n        display: none;\n      }\n    }\n    &:hover {\n      .hideable {\n        display: inline-block;\n      }\n    }\n  }\n  @media (max-width: 480px) {\n    .reply {\n      padding: 10px;\n      margin: 0 -10px;\n      padding-left: 65px;\n    }\n  }\n}\n\n.edit-reply {\n  form {\n    .btn-primary {\n      margin-right: 10px;\n    }\n  }\n}\n\n#node-selector {\n  .card {\n    border: 0;\n    box-shadow: 0 0 0;\n    padding: 0;\n    margin: 0;\n  }\n  .card-header {\n    display: none;\n  }\n  .card-body {\n    padding: 0 20px;\n    margin: 0;\n  }\n}\n\n#node-selector-button {\n  border-color: #ced4da;\n}\n\n#notifications {\n  .card-header {\n    .clean-button {\n      margin-left: 10px;\n    }\n  }\n  .notification {\n    position: relative;\n    margin-bottom: 1.25rem;\n    padding-bottom: 1.25rem;\n    border-bottom: 1px solid $gray;\n    &:last-child {\n      margin-bottom: 0px;\n      border-bottom: 0px;\n      padding-bottom: 0px;\n    }\n    .unread {\n      color: $blue;\n      font-size: 10px;\n      position: absolute;\n      right: 5px;\n      top: 20px;\n    }\n    .avatar {\n      text-align: center;\n    }\n    .info {\n      color: #999;\n      margin-bottom: 8px;\n      font-size: 14px;\n    }\n    .date {\n      font-size: 14px;\n      color: #aaa;\n    }\n  }\n}\n\n.sidebar {\n  .card {\n    margin-bottom: 1.25rem;\n  }\n  .card-body {\n    word-break: break-all;\n  }\n}\n\n.api-doc {\n  .route-list {\n    padding: 20px 0;\n    border-right: 2px dashed #ddd;\n    li {\n      line-height: 200%;\n      color: #999;\n      a:link,\n      a:visited {\n        color: #404040 !important;\n        text-decoration: underline !important;\n      }\n    }\n  }\n  .route {\n    margin-top: 1.25rem;\n    h5 {\n      color: #333;\n      border-bottom: 1px solid $gray;\n      margin: 0;\n      margin-bottom: 10px;\n      padding: 5px 0 0 0;\n      label {\n        font-size: 12px;\n        font-weight: normal;\n        display: inline-block;\n        width: 50px;\n        color: $gray;\n      }\n    }\n    .content {\n      margin: 0 1.25rem;\n    }\n    .desc {\n      h4 {\n        border: 0px;\n        font-size: 13px !important;\n        margin: 0;\n        color: #999;\n      }\n    }\n    h6 {\n      color: #999;\n    }\n    table.params {\n      td.field {\n        width: 80px;\n      }\n      td.type {\n        width: 70px;\n      }\n      td.required {\n        width: 50px;\n      }\n      td.values {\n        width: 180px;\n      }\n      td.default {\n        width: 100px;\n      }\n    }\n  }\n}\n\n// Fix searchbox style\n.bs-searchbox .form-control {\n  float: none;\n}\n\n@media (min-width: 744px) and (max-width: 1200px) {\n  .sidebar .card .card-body .feed-button {\n    float: none !important;\n    margin-top: 1.25rem;\n  }\n}\n\n/* Social Share Button */\n\n.social-share-button {\n  height: 16px;\n  a {\n    i.fa {\n      font-size: 24px;\n      margin: 0 4px;\n    }\n    &:link,\n    &:visited {\n      color: #777;\n    }\n    &:hover {\n      color: $blue;\n    }\n  }\n}\n\n.popover-content {\n  .social-share-button {\n    display: block;\n  }\n}\n\n/* Markdown Styles */\n\n.markdown {\n  position: relative;\n  letter-spacing: .03em;\n  font-size: 15px;\n  text-overflow: ellipsis;\n  word-wrap: break-word;\n  a {\n    color: $blue;\n  }\n  img,\n  iframe {\n    max-width: 100%;\n    border: 0;\n  }\n  p,\n  pre,\n  ul,\n  ol,\n  hr,\n  blockquote {\n    margin-bottom: 20px;\n  }\n  p:last-child,\n  blockquote:last-child,\n  pre:last-child,\n  ul:last-child,\n  ol:last-child,\n  hr:last-child {\n    margin-bottom: 0;\n  }\n  p {\n    font-size: 15px;\n    line-height: 26px;\n  }\n  hr {\n    border: 2px dashed $gray;\n    border-bottom: 0px;\n    margin-left: auto;\n    margin-right: auto;\n    width: 50%;\n  }\n  blockquote {\n    margin-left: 0 15px 15px 15px;\n    padding: 0;\n    padding-left: 32px;\n    border: 0px;\n    quotes: \"\\201C\"\"\\201D\"\"\\2018\"\"\\2019\";\n    position: relative;\n    font-size: 15px;\n    line-height: 1.45;\n    p {\n      display: inline;\n      color: #999;\n    }\n    &:before,\n    &:after {\n      display: block;\n      content: \"\\201C\";\n      font-size: 35px;\n      position: absolute;\n      font-family: serif;\n      left: 0px;\n      top: 0px;\n      color: #aaa;\n    }\n  }\n  pre {\n    font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n    font-size: 12px;\n    background-color: #f9f9f9;\n    border: 0px;\n    border-top: 1px solid #f0f0f0;\n    border-bottom: 1px solid #f0f0f0;\n    margin: 0 -15px 15px -15px;\n    padding: 10px 15px;\n    color: #444;\n    overflow: auto;\n    border-radius: 0px;\n    code {\n      display: block;\n      line-height: 150%;\n      padding: 0 !important;\n      font-size: 12px !important;\n      background-color: #f9f9f9 !important;\n      border: none !important;\n    }\n  }\n  code {\n    display: inline-block;\n    font-size: 12px !important;\n    background-color: #f5f5f5 !important;\n    border: 0px;\n    color: #444 !important;\n    padding: 1px 4px !important;\n    margin: 2px;\n    border-radius: 3px;\n    word-break: break-all;\n    line-height: 20px;\n  }\n  a:link,\n  a:visited {\n    color: $blue !important;\n    text-decoration: none !important;\n  }\n  a:hover {\n    text-decoration: underline !important;\n    color: $primary !important;\n  }\n  a.mention-floor {\n    color: #60b566 !important;\n    margin-right: 3px;\n  }\n  a.mention {\n    color: #777 !important;\n    font-weight: bold;\n    margin-right: 2px;\n    b {\n      color: #777 !important;\n      font-weight: normal;\n    }\n  }\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6 {\n    font-weight: bold;\n    text-align: left;\n    margin-top: 0px;\n    margin-bottom: 20px;\n  }\n  h1 {\n    font-size: 26px !important;\n    text-align: center;\n    margin-bottom: 30px !important;\n  }\n  h2,\n  h3,\n  h4 {\n    text-align: left;\n    font-weight: bold;\n    font-size: 16px !important;\n    line-height: 100%;\n    margin: 0;\n    color: #555;\n    margin-bottom: 20px;\n    border-bottom: 1px solid #eee;\n    padding-bottom: 10px;\n  }\n  h2 {\n    font-size: 20px !important;\n    color: #111;\n  }\n  h3 {\n    font-size: 18px !important;\n    color: #333;\n  }\n  h5,\n  h6 {\n    font-size: 15px;\n    line-height: 100%;\n    color: #777;\n  }\n  h6 {\n    font-size: 14px;\n    color: #999;\n  }\n  strong {\n    color: #000;\n  }\n  ul,\n  ol {\n    list-style-type: square;\n    margin: 0;\n    margin-bottom: 20px;\n    padding: 0px 20px;\n    p,\n    blockquote,\n    pre {\n      margin-bottom: 8px;\n    }\n    li {\n      line-height: 1.6em;\n      padding: 2px 0;\n      color: #333;\n    }\n    ul {\n      list-style-type: circle;\n      margin-bottom: 0px;\n    }\n  }\n  ol {\n    list-style-type: decimal;\n    ol {\n      list-style-type: lower-alpha;\n      margin-bottom: 0px;\n    }\n  }\n  img {\n    vertical-align: top;\n    max-width: 100%;\n  }\n  a.zoom-image {\n    cursor: zoom-in;\n  }\n  a.at_floor {\n    color: #60B566 !important;\n  }\n  a.at_user,\n  a.user-mention {\n    color: #0069D6 !important;\n  }\n  img.twemoji {\n    width: 20px;\n  }\n}\n\n.notify-updated {\n  display: none;\n  padding: 4px 15px;\n  margin-bottom: 20px;\n  text-align: left;\n  background: #FDF8A6;\n  border: 1px solid #F5E3A4;\n  color: $red;\n  a:link,\n  a:visited {\n    color: $yellow;\n  }\n}\n\n.dz-preview {\n  display: none;\n}\n\ntextarea.div-dropzone-focus {\n  border-color: #BBE1C9;\n  background: #fafafa;\n}\n\n#dropdown-insert-codes {\n  .dropdown-toggle::after {\n    display: none;\n  }\n}\n\n.emoji-modal {\n  .modal-dialog {\n    max-width: 496px;\n  }\n  .modal-header {\n    border: 0px;\n    padding: 8px;\n  }\n  .modal-body {\n    padding: 0 8px 8px 8px;\n  }\n  .twemoji {\n    width: 20px;\n    height: 20px;\n  }\n  .nav>li>a {\n    padding: 5px 8px;\n  }\n  .nav-tabs li:first-child {\n    margin-left: 8px;\n  }\n  .tab-pane {\n    padding: 0px;\n    height: 180px;\n    overflow: scroll;\n    &::-webkit-scrollbar {\n      width: 4px;\n      border-radius: 3px;\n    }\n    &::-webkit-scrollbar-thumb {\n      background: #e0e0e0;\n    }\n    a {\n      padding: 5px;\n      display: inline-block;\n      width: 30px;\n      height: 30px;\n      &:hover {\n        background: #f0f0f0;\n      }\n    }\n  }\n  .modal-footer {\n    padding: 8px;\n    text-align: left;\n    font-size: 16px;\n    .emoji {\n      width: 48px;\n      height: 48px;\n      margin-right: 10px;\n    }\n  }\n}\n\n.popover-liked-users {\n  .avatar-16 {\n    display: inline-block;\n    margin: 5px 0;\n  }\n}\n\n.comments {\n  .comment {\n    padding: 15px;\n    margin: 0 -15px;\n    border-bottom: 1px solid #f0f0f0;\n    &:first-child {\n      padding-top: 0;\n    }\n    &:last-child {\n      border: 0;\n      padding-bottom: 0;\n    }\n    .info {\n      font-size: 13px;\n      color: #999;\n    }\n  }\n}\n\n.card-body {\n  .heading {\n    font-size: 16px;\n    color: #777;\n    font-weight: bold;\n    padding-bottom: 10px;\n    border-bottom: 1px solid #eee;\n    margin-bottom: 15px;\n  }\n  form {\n    margin-bottom: 25px;\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n}\n\n.reward-image {\n  border-radius: 3px;\n  padding: 20px;\n  background: #FFF;\n  text-align: center;\n  img {\n    max-width: 240px;\n  }\n}\n\n#reward-modal {\n  padding-top: 50px;\n  text-align: center;\n  .modal-dialog {\n    max-width: 750px;\n    min-height: 300px;\n  }\n  .reward-image {\n    display: inline-block;\n    padding: 0px 20px 10px 20px;\n  }\n  .message {\n    margin: 10px auto 0 auto;\n    max-width: 580px;\n    font-size: 16px;\n    text-align: center;\n    .user-info {\n      margin-bottom: 15px;\n    }\n    .media-object {\n      display: inline-block;\n    }\n    i.fa {\n      color: #aaa;\n    }\n  }\n}\n\n.alert {\n  box-shadow: 0 0 0;\n  padding: 0.65rem 1.25rem;\n  border: 2px solid transparent !important;\n  .close {\n    color: $primary;\n    opacity: .7;\n  }\n}\n\n.alert-block {\n  ul {\n    margin-bottom: 0;\n    margin-top: 1.25em;\n  }\n}\n\n.alert-danger {\n  color: $red;\n  background: lighten($red, 43%);\n}\n\n.alert-warning {\n  color: darken($yellow, 10%);\n  background: lighten($yellow, 45%);\n}\n\n.alert-success {\n  color: $green;\n  background: lighten($green, 65%);\n}\n\n.alert-info {\n  color: $blue;\n  background: lighten($blue, 45%);\n}"
  },
  {
    "path": "assets/css/app/components/page.scss",
    "content": "#home_index {\n  line-height: 160%;\n}\n\n.home-icons {\n  .item {\n    text-align: center;\n    margin-bottom: 15px;\n    border-radius: 3px;\n    background: #FFF;\n    border: 1px;\n    border-color: #e5e6e9 #dfe0e4 #d0d1d5 #dfe0e4;\n    .icon {\n      display: block;\n      a {\n        display: block;\n        padding: 20px 15px;\n      }\n      .fa {\n        font-size: 60px;\n      }\n    }\n    .text {\n      display: block;\n      text-align: left;\n      background: #F5F5F5;\n      border-top: 1px solid #E9E9E9;\n      font-size: 14px;\n      font-weight: bold;\n      a {\n        display: block;\n        color: #666;\n        padding: 6px 15px;\n      }\n      a:hover {\n        text-decoration: none;\n      }\n      .fa {\n        margin-top: 3px;\n      }\n      border-radius: 0 0 3px 3px;\n    }\n    &:hover {\n      opacity: 0.75;\n    }\n  }\n  .item1 {\n    .icon {\n      .fa {\n        color: lighten($grape, 10%);\n      }\n    }\n  }\n  .item3 {\n    .icon {\n      .fa {\n        color: lighten($grape, 10%);\n      }\n    }\n  }\n  .item2 {\n    .icon {\n      .fa {\n        color: lighten($yellow, 10%);\n      }\n    }\n  }\n  .item4 {\n    .icon {\n      .fa {\n        color: lighten($green, 10%);\n      }\n    }\n  }\n}\n\nh2 {\n  font-size: 12px;\n  color: #999;\n  line-height: 100%;\n  margin-bottom: 10px;\n  text-align: center;\n}\n\n#last_topics {\n  float: left;\n  width: 450px;\n}\n\n#hot_topics {\n  float: right;\n  width: 450px;\n}\n\n.node-topics {\n  margin-bottom: 0px;\n  .head {\n    display: none;\n  }\n}\n\n.location-list {\n  .name {\n    a {\n      color: #666;\n      margin: 6px;\n      display: inline-block;\n    }\n  }\n}\n\n.home_suggest_topics {\n  .topics {\n    .topic {\n      .title {\n        height: 30px;\n        overflow: hidden;\n      }\n    }\n  }\n}"
  },
  {
    "path": "assets/css/app/components/user.scss",
    "content": ".subnav {\n  margin-bottom: -18px;\n  .nav-tabs {\n    border-bottom: 0px;\n    padding-left: 20px;\n  }\n  .nav-tabs>li>a:hover {\n    border-color: transparent;\n    background: none;\n    text-decoration: underline;\n  }\n  .nav-tabs>.active>a,\n  .nav-tabs>.active>a:hover {\n    color: #555555;\n    background-color: #ffffff;\n    border: 1px solid #ddd;\n    border-bottom-color: transparent;\n    cursor: default;\n  }\n}\n\n.page-users {\n  .nav-tabs {\n    border-bottom: 0px;\n  }\n  @media (max-width: 480px) {\n    .sidebar {\n      display: block;\n    }\n  }\n}\n\n.node-topics {\n  border-bottom: 1px solid #ddd;\n  tbody>tr>td {\n    padding: 8px;\n    color: #666;\n  }\n  td.title {\n    a:link,\n    a:visited {\n      font-size: 15px;\n      text-decoration: none;\n    }\n    a:hover {\n      text-decoration: underline;\n    }\n    em {\n      font-style: normal;\n      font-size: 12px;\n      color: #bbb;\n    }\n    i.icon {\n      margin-bottom: -1px;\n    }\n  }\n  td.node {\n    a {\n      color: #666;\n    }\n  }\n  tr.head {\n    td {\n      border-top: none;\n      padding-top: 14px;\n      color: #CCC;\n      font-weight: bold;\n      font-size: 12px;\n    }\n  }\n  tr.odd {\n    td {\n      background: #fafafa;\n    }\n  }\n  tr.topic {\n    td.author {\n      width: 80px;\n      a {\n        color: #666;\n        font-weight: bold;\n      }\n    }\n  }\n}\n\n.recent-topics {\n  ul {\n    li {\n      .title {\n        font-size: 14px;\n      }\n      i.icon {\n        margin-bottom: -1px;\n      }\n      i.fa-diamond {\n        color: $red;\n      }\n      .info {\n        margin-top: 3px;\n        font-size: 12px;\n        color: #bbb;\n      }\n      .node {\n        margin-right: 5px;\n        color: #777;\n        margin-right: 3px;\n      }\n    }\n  }\n}\n\n.recent-replies {\n  margin-bottom: 0;\n  li {\n    .title {\n      font-size: 15px;\n      .info {\n        font-size: 14px;\n        color: lighten($secondary, 20%);\n      }\n    }\n    .body {\n      a {\n        color: $primary;\n      }\n      margin-top: 6px;\n      color: $secondary;\n      p {\n        font-size: 14px;\n      }\n      img {\n        max-width: 680px;\n      }\n    }\n  }\n}\n\n.row>.span13 {\n  margin-left: 0;\n}\n\n#main .userinfo h1 {\n  text-align: left;\n  display: inline;\n}\n\n.userinfo {\n  .tagline {\n    text-align: left;\n    margin-top: -8px;\n    margin-bottom: 20px;\n  }\n  .media-right {\n    padding-left: 15px;\n    text-align: center;\n    .avatar {\n      margin-bottom: 10px;\n    }\n  }\n  .list-group {\n    margin-bottom: 0px;\n  }\n  li {\n    border-color: #f0f0f0;\n    font-size: 13px;\n    label {\n      color: #999;\n      margin-right: 8px;\n      display: inline-block;\n      width: 80px;\n      text-align: right;\n    }\n  }\n}\n\n.bio {\n  font-size: 12px;\n  line-height: 180%;\n  p:last-child {\n    margin-bottom: 0;\n  }\n}\n\n.replies ul {\n  margin: 0;\n  h6 {\n    color: #999;\n    font-weight: normal;\n  }\n  li {\n    line-height: 180%;\n    border-bottom: 1px solid #ddd;\n    list-style: none;\n  }\n  blockquote {\n    line-height: 160%;\n  }\n}\n\n.content>.tabs {\n  border-bottom: 2px solid #ccc;\n  .active {\n    margin-bottom: 0;\n  }\n}\n\ntable.node-topics {\n  td {\n    a {\n      color: #333;\n      font-weight: normal;\n    }\n    i.fa-diamond {\n      color: $grape;\n    }\n  }\n  td.replied-at {\n    width: 80px;\n  }\n}\n\n.user-list {\n  h2 {\n    font-size: 14px;\n    margin: 0;\n  }\n  .user {\n    text-align: center;\n    margin-bottom: 20px;\n    overflow: hidden;\n    ;\n    .avatar {\n      img {\n        width: 48px;\n        height: 48px;\n        margin: 0 auto;\n      }\n      margin-bottom: 5px;\n    }\n    .name {\n      a {\n        color: #333;\n      }\n    }\n  }\n}\n\n.bloced-users {\n  .item {\n    text-align: left;\n    margin-bottom: 10px;\n    .media-object {\n      display: inline;\n    }\n  }\n}\n\n.sidebar {\n  padding-right: 0;\n  .profile {\n    .avatar {\n      .level {\n        margin-top: 6px;\n        text-align: center;\n      }\n    }\n    .item {\n      margin-bottom: 5px;\n    }\n    .item a {\n      color: #666;\n    }\n    .number {\n      color: #999;\n    }\n    .counts {\n      color: #999;\n      span {\n        color: #666;\n      }\n    }\n    .follow-info {\n      border-top: 1px solid #f0f0f0;\n      text-align: center;\n      margin-top: 15px;\n      padding-top: 15px;\n      a {\n        display: block;\n        text-decoration: none;\n      }\n      a.counter {\n        font-size: 32px;\n        color: $primary;\n        &:hover {\n          color: $primary;\n        }\n      }\n      a.text {\n        color: #999;\n      }\n    }\n    .buttons {\n      border-top: 1px solid #f0f0f0;\n      margin-top: 15px;\n      padding-top: 15px;\n    }\n    .social {\n      font-size: 18px;\n      a {\n        color: #999;\n        margin-right: 8px;\n      }\n      a:hover {\n        color: #666;\n      }\n    }\n    .tagline {\n      border-top: 1px solid #f0f0f0;\n      margin-top: 10px;\n      color: #999;\n      line-height: 100%;\n      padding: 10px;\n      padding-bottom: 0;\n    }\n  }\n  .user-teams {\n    .media-object {\n      display: inline-block;\n      margin: 4px 2px;\n    }\n  }\n}\n\n.user-card {\n  margin-bottom: 15px;\n  padding-left: 15px;\n  .media-heading {\n    font-weight: bold;\n    a {\n      color: #333;\n    }\n  }\n  .infos {\n    color: #999;\n    font-size: 12px;\n    .item {\n      margin-top: 5px;\n    }\n  }\n}\n\n.user-profile-fields {\n  margin-top: 20px;\n  border-top: 1px solid #eee;\n  padding-top: 20px;\n  .field {\n    padding: 2px 0;\n    label {\n      color: #666;\n      display: inline-block;\n      width: 100px;\n      margin-right: 10px;\n    }\n    .value {\n      a {\n        text-decoration: underline;\n      }\n    }\n  }\n}\n\n#user_github_repos {\n  .more {\n    text-align: right;\n  }\n  ul {\n    margin: 0;\n  }\n  li {\n    .title {\n      position: relative;\n      margin-bottom: 5px;\n      a {\n        color: #333;\n        font-weight: bold;\n      }\n      .watchers {\n        position: absolute;\n        top: 2px;\n        right: 0;\n        color: #999;\n      }\n    }\n    .desc {\n      font-size: 12px;\n      color: #888;\n      padding: 0;\n      margin: 0;\n    }\n  }\n}\n\n.user-activity-graph {\n  overflow-x: scroll;\n  text-align: center;\n  svg {\n    margin: 0 auto;\n  }\n}\n\n.avatar-preview {\n  .media-object {\n    display: inline-block;\n  }\n}"
  },
  {
    "path": "assets/css/app.scss",
    "content": "$fa-font-path: \"~font-awesome/fonts\";\n@import \"~font-awesome/scss/font-awesome.scss\";\n@import \"~bootstrap/scss/bootstrap\";\n@import \"./common/variables/mipha\";\n@import \"./common/components/header\";\n@import \"./app/components/footer\";\n@import \"./app/components/base\";\n@import \"./app/components/page\";\n@import \"./app/components/user\";\n@import \"./app/components/notification\";\n// import jquery.atwho\n@import \"../static/atwho/jquery.atwho.min.css\";"
  },
  {
    "path": "assets/css/common/components/_header.scss",
    "content": ".bd-navbar {\n  height: 50px;\n  padding: 0;\n  background: #FFF;\n  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n  border: 0px;\n  color: $primary;\n  z-index: 1030;\n  line-height: 100%;\n  .ml-auto {\n    height: 50px;\n  }\n  .navbar-nav {\n    padding: 0px;\n    .nav-link {\n      padding: 15px 15px;\n      color: $primary;\n    }\n  }\n  .navbar-topic-title {\n    display: none;\n  }\n  &.navbar-fixed-active {\n    box-shadow: 0 1px 1px rgba(0, 0, 0, .07);\n  }\n  .navbar-brand {\n    line-height: 100%;\n    color: #666 !important;\n    font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n    font-weight: bold;\n    margin-left: 1em;\n    border-bottom: 0px;\n    b {\n      color: $grape;\n    }\n  }\n  @include media-breakpoint-down(md) {\n    padding-right: .5rem;\n    padding-left: .5rem;\n    .navbar-nav-scroll {\n      max-width: 100%;\n      overflow: hidden;\n      .navbar-nav {\n        overflow-x: auto;\n        white-space: nowrap;\n        -webkit-overflow-scrolling: touch;\n      }\n    }\n  }\n  @include media-breakpoint-up(md) {\n    @supports (position: sticky) {\n      position: sticky;\n      top: 0;\n    }\n  }\n  #main-nav-menu {\n    &.navbar-nav .nav-item a.nav-link {\n      color: $primary;\n      font-size: 15px;\n      border-top: 3px solid #fff;\n      border-bottom: 3px solid #FFF;\n      transition: all .2s ease-in-out;\n    }\n    &.navbar-nav .nav-item a.nav-link:active {\n      background-color: $gray;\n      border-bottom-color: #999;\n    }\n    &.navbar-nav .nav-item {\n      a:hover,\n      a.active,\n      a.active:focus,\n      a.active:hover {\n        color: $grape;\n        background: transparent;\n        border-bottom-color: $grape;\n      }\n    }\n  }\n  .form-search {\n    font-size: 14px;\n    position: relative;\n    padding: 15px 0;\n    .fa {\n      color: #333;\n      &:hover {\n        color: #666;\n      }\n    }\n    .fa-search {\n      cursor: pointer;\n      position: absolute;\n      top: 18px;\n      left: 8px;\n      transition: all .3s;\n    }\n    .form-control {\n      font-size: 14px;\n      border: none;\n      cursor: text;\n      width: 150px;\n      height: 100%;\n      line-height: 100%;\n      padding: 6px 1px 4px 20px;\n      margin-left: 4px;\n      background: transparent;\n      transition: all .3s;\n      box-sizing: border-box;\n      color: #333;\n      &::-webkit-input-placeholder {\n        color: #aaa;\n      }\n    }\n  }\n  .navbar-nav-svg {\n    display: inline-block;\n    width: 1rem;\n    height: 1rem;\n    vertical-align: text-top;\n  }\n  .dropdown.show {\n    background: rgba(0, 0, 0, 0.04);\n  }\n  .dropdown-menu {\n    border-radius: 0;\n    border: 0px;\n    border-left: 1px solid rgba(0, 0, 0, 0.04);\n    box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.10);\n    .dropdown-item {\n      padding: 0.4rem 1.5rem;\n    }\n  }\n  .dropdown-avatar {\n    line-height: 100%;\n    display: inline-block;\n    .nav-link {\n      padding: 10px 5px;\n    }\n    img.avatar-32 {\n      width: 20px;\n      height: 20px;\n    }\n  }\n  .dropdown-item.active {\n    font-weight: 500;\n    color: $gray-900;\n    background-color: rgba(0, 0, 0, 0.04);\n  }\n  .user-bar {\n    background: #FFF;\n    min-width: 150px;\n  }\n  @include media-breakpoint-down(md) {\n    .user-bar {\n      .nav-link {\n        padding-left: 5px;\n        padding-right: 5px;\n      }\n    }\n  }\n}"
  },
  {
    "path": "assets/css/common/variables/mipha.scss",
    "content": "$primary: #404040;\n$default: #e0e0e0;\n$secondary: #666;\n$gray: #F2F2F2;\n$red: #EB5424;\n$yellow: #FEE209;\n$blue: #2275da;\n$grape: #9A54B1;\n$green: #19A302;\n$body-bg: #FFF;\n$theme-colors: ( \"primary\": $blue, \"warning\": $yellow, );\n$input-border: $default;\n$input-color-placeholder: $default;\n$link-color: $primary;\n$colors: ( \"blue\": $blue, \"grape\": $grape, \"red\": $red, \"yellow\": $yellow, \"green\": $green, \"white\": #FFF, \"gray\": $gray, \"gray-dark\": $gray) !default;\n$font-size-base: 0.875rem;\n$spacer: 15px;\n/* Animations */\n\n.animated {\n  -webkit-animation-duration: 0.5s;\n  animation-duration: 0.5s;\n  -webkit-animation-fill-mode: both;\n  animation-fill-mode: both;\n}\n\n@-webkit-keyframes bounceIn {\n  0% {\n    opacity: 0;\n    -webkit-transform: scale(.5);\n  }\n  50% {\n    opacity: 1;\n    -webkit-transform: scale(1.5);\n  }\n  70% {\n    -webkit-transform: scale(.9);\n  }\n  100% {\n    -webkit-transform: scale(1);\n  }\n}\n\n@keyframes bounceIn {\n  0% {\n    opacity: 0;\n    transform: scale(.5);\n  }\n  50% {\n    opacity: 1;\n    transform: scale(1.5);\n  }\n  70% {\n    transform: scale(.9);\n  }\n  100% {\n    transform: scale(1);\n  }\n}\n\n.bounceIn {\n  -webkit-animation-name: bounceIn;\n  animation-name: bounceIn;\n}"
  },
  {
    "path": "assets/js/admin.js",
    "content": "import 'phoenix_html'\nimport 'bootstrap'\n\nimport Utils from './common/components/utils'\n\nUtils.navActive()\n"
  },
  {
    "path": "assets/js/app/components/editor.js",
    "content": "export default class Editor {\n  constructor() {\n    this.appendCodesFromHint()\n    this.initDropzone()\n    this.browseUpload()\n  }\n\n  initDropzone() {\n    const self = this\n    const editor = $('textarea.topic-editor')\n    editor.wrap('<div class=\"topic-editor-dropzone\"></div>')\n    const editor_dropzone = $('.topic-editor-dropzone')\n    editor_dropzone.on(\n      'paste',\n      (function(_this) {\n        return function(event) {\n          self.handlePaste(event)\n        }\n      })(this)\n    )\n    editor_dropzone.dropzone({\n      url: '/api/callback/qiniu',\n      dictDefaultMessage: '',\n      clickable: true,\n      paramName: 'file',\n      maxFilesize: 20,\n      uploadMultiple: false,\n      headers: {\n        'X-CSRF-Token': $('meta[name=\"csrf-token\"]').attr('content')\n      },\n      previewContainer: false,\n      processing: function() {\n        $('.div-dropzone-alert').alert('close')\n        self.showUploading()\n      },\n      dragover: function() {\n        editor.addClass('div-dropzone-focus')\n      },\n      dragleave: function() {\n        editor.removeClass('div-dropzone-focus')\n      },\n      drop: function() {\n        editor.removeClass('div-dropzone-focus')\n        editor.focus()\n      },\n      success: function(header, res) {\n        self.appendImageFromUpload([res.qn_url])\n      },\n      error: function(temp, msg) {},\n      totaluploadprogress: function(num) {},\n      sending: function() {},\n      queuecomplete: function() {\n        self.restoreUploaderStatus()\n      }\n    })\n  }\n\n  browseUpload() {\n    $('#editor-upload-image').click(function() {\n      $('.topic-editor').focus()\n      $('.topic-editor-dropzone').click()\n    })\n  }\n\n  uploadFile(item, filename) {\n    const self = this\n    const formData = new FormData()\n    formData.append('file', item, filename)\n    $.ajax({\n      url: '/api/callback/qiniu',\n      type: 'POST',\n      data: formData,\n      dataType: 'JSON',\n      processData: false,\n      contentType: false,\n      beforeSend: function() {\n        self.showUploading()\n      },\n      success: function(e, status, res) {\n        self.appendImageFromUpload([res.qn_url])\n        self.restoreUploaderStatus()\n      },\n      error: function(res) {\n        self.restoreUploaderStatus()\n      },\n      complete: function() {\n        self.restoreUploaderStatus()\n      }\n    })\n  }\n\n  handlePaste(e) {\n    const self = this\n    const pasteEvent = e.originalEvent\n    if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {\n      const image = this.isImage(pasteEvent)\n      if (image) {\n        e.preventDefault()\n        self.uploadFile(image.getAsFile(), 'image.png')\n      }\n    }\n  }\n\n  isImage(data) {\n    let i\n    while (i < data.clipboardData.items.length) {\n      const item = data.clipboardData.items[i]\n      if (item.type.indexOf('image') !== -1) {\n        return item\n      }\n      i++\n    }\n  }\n\n  showUploading() {\n    $('#editor-upload-image').hide()\n    if (\n      $('#editor-upload-image')\n        .parent()\n        .find('span.loading').length === 0\n    ) {\n      $('#editor-upload-image').before(\n        \"<span class='loading'><i class='fa fa-circle-o-notch fa-spin'></i></span>\"\n      )\n    }\n  }\n\n  appendImageFromUpload(srcs) {\n    let j, len\n    let src_merged = ''\n    for (j = 0, len = srcs.length; j < len; j++) {\n      const src = srcs[j]\n      src_merged = '![](' + src + ')\\n'\n    }\n    this.insertString(src_merged)\n  }\n\n  restoreUploaderStatus() {\n    $('#editor-upload-image')\n      .parent()\n      .find('span.loading')\n      .remove()\n    $('#editor-upload-image').show()\n  }\n\n  insertString(str) {\n    const $target = $('.topic-editor')\n    const start = $target[0].selectionStart\n    const end = $target[0].selectionEnd\n    $target.val(\n      $target.val().substring(0, start) + str + $target.val().substring(end)\n    )\n    $target[0].selectionStart = $target[0].selectionEnd = start + str.length\n    $target.focus()\n  }\n\n  appendCodesFromHint() {\n    $('.insert-codes a').click(function(e) {\n      const link = $(e.currentTarget)\n      const language = link.data('lang')\n      const txtBox = $('.topic-editor')\n      const caret_pos = txtBox.caret('pos')\n      let prefix_break = ''\n      if (txtBox.val().length > 0) {\n        prefix_break = '\\n'\n      }\n      const src_merged = prefix_break + '```' + language + '\\n\\n```\\n'\n      const source = txtBox.val()\n      const before_text = source.slice(0, caret_pos)\n      txtBox.val(\n        before_text + src_merged + source.slice(caret_pos + 1, source.count)\n      )\n      txtBox.caret('pos', caret_pos + src_merged.length - 5)\n      txtBox.focus()\n      txtBox.trigger('click')\n    })\n  }\n}\n"
  },
  {
    "path": "assets/js/app/components/session.js",
    "content": "export default class Session {\n  static refreshExcaptcha() {\n    $('a.excaptcha-image-box').click(function(e) {\n      const img = $(e.currentTarget).find('img:first')\n      const currentSrc = img.attr('src')\n      img.attr('src', currentSrc.split('?')[0] + '?' + new Date().getTime())\n    })\n  }\n}\n"
  },
  {
    "path": "assets/js/app/components/times.js",
    "content": "const moment = require('moment')\nmoment.locale('zh-cn', {\n  months: '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split(\n    '_'\n  ),\n  monthsShort: '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),\n  weekdays: '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),\n  weekdaysShort: '周日_周一_周二_周三_周四_周五_周六'.split('_'),\n  weekdaysMin: '日_一_二_三_四_五_六'.split('_'),\n  longDateFormat: {\n    LT: 'Ah点mm分',\n    LTS: 'Ah点m分s秒',\n    L: 'YYYY-MM-DD',\n    LL: 'YYYY年MMMD日',\n    LLL: 'YYYY年MMMD日Ah点mm分',\n    LLLL: 'YYYY年MMMD日ddddAh点mm分',\n    l: 'YYYY-MM-DD',\n    ll: 'YYYY年MMMD日',\n    lll: 'YYYY年MMMD日Ah点mm分',\n    llll: 'YYYY年MMMD日ddddAh点mm分'\n  },\n  meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,\n  meridiemHour: function(h, meridiem) {\n    let hour = h\n    if (hour === 12) {\n      hour = 0\n    }\n    if (meridiem === '凌晨' || meridiem === '早上' || meridiem === '上午') {\n      return hour\n    } else if (meridiem === '下午' || meridiem === '晚上') {\n      return hour + 12\n    } else {\n      // '中午'\n      return hour >= 11 ? hour : hour + 12\n    }\n  },\n  meridiem: function(hour, minute, isLower) {\n    const hm = hour * 100 + minute\n    if (hm < 600) {\n      return '凌晨'\n    } else if (hm < 900) {\n      return '早上'\n    } else if (hm < 1130) {\n      return '上午'\n    } else if (hm < 1230) {\n      return '中午'\n    } else if (hm < 1800) {\n      return '下午'\n    } else {\n      return '晚上'\n    }\n  },\n  calendar: {\n    sameDay: function() {\n      return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT'\n    },\n    nextDay: function() {\n      return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT'\n    },\n    lastDay: function() {\n      return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT'\n    },\n    nextWeek: function() {\n      let startOfWeek, prefix\n      startOfWeek = moment().startOf('week')\n      prefix = this.diff(startOfWeek, 'days') >= 7 ? '[下]' : '[本]'\n      return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'\n    },\n    lastWeek: function() {\n      let startOfWeek, prefix\n      startOfWeek = moment().startOf('week')\n      prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]'\n      return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'\n    },\n    sameElse: 'LL'\n  },\n  ordinalParse: /\\d{1,2}(日|月|周)/,\n  ordinal: function(number, period) {\n    switch (period) {\n      case 'd':\n      case 'D':\n      case 'DDD':\n        return number + '日'\n      case 'M':\n        return number + '月'\n      case 'w':\n      case 'W':\n        return number + '周'\n      default:\n        return number\n    }\n  },\n  relativeTime: {\n    future: '%s内',\n    past: '%s前',\n    s: '几秒',\n    m: '1 分钟',\n    mm: '%d 分钟',\n    h: '1 小时',\n    hh: '%d 小时',\n    d: '1 天',\n    dd: '%d 天',\n    M: '1 个月',\n    MM: '%d 个月',\n    y: '1 年',\n    yy: '%d 年'\n  },\n  week: {\n    // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效\n    dow: 1, // Monday is the first day of the week.\n    doy: 4 // The week that contains Jan 4th is the first week of the year.\n  }\n})\n\nexport default class Times {\n  static humanize() {\n    $('addr.timeago').html((_, html) => {\n      return moment.utc(html.trim(), moment.ISO_8601).fromNow()\n    })\n  }\n}\n"
  },
  {
    "path": "assets/js/app/components/topic.js",
    "content": "import 'jquery'\nimport 'jquery.caret'\nimport _ from 'lodash'\nimport 'static/atwho/jquery.atwho.min'\n\nconst selectorNode = () => {\n  $('#node-selector .nodes .name a').click(function(e) {\n    const el = $(e.currentTarget)\n    $('#node-selector').modal('hide')\n    $('form input[name=\"topic[title]\"]').focus()\n    if ($('form input[name=\"topic[node_id]\"]').length > 0) {\n      e.preventDefault()\n      const nodeId = el.data('id')\n      $('form input[name=\"topic[node_id]\"]').val(nodeId)\n      $('#node-selector-button').html(el.text())\n      return false\n    } else {\n      return true\n    }\n  })\n}\n\nconst hookPreview = (switcher, textarea) => {\n  const preview_box = $(document.createElement('div')).attr('id', 'preview')\n  preview_box.addClass('markdown form-control')\n  $(textarea).after(preview_box)\n  preview_box.hide()\n  $('.preview', switcher).click(function() {\n    if ($(this).hasClass('active')) {\n      $(this).removeClass('active')\n      $(preview_box).hide()\n      $(textarea).show()\n    } else {\n      $(this).addClass('active')\n      $(preview_box).show()\n      $(textarea).hide()\n      $(preview_box).css('height', $(textarea).height())\n      preview($(textarea).val())\n    }\n  })\n}\n\nconst preview = body => {\n  $('#preview').text('Loading...')\n  $.post(\n    '/api/topics/preview',\n    {\n      body: body\n    },\n    function(data) {\n      return $('#preview').html(data.body)\n    },\n    'json'\n  )\n}\n\nconst hookReply = () => {\n  $('.btn-reply').click(function() {\n    const replyId = $(this).data('id')\n    setReplyTo(replyId)\n    const reply_body = $('#new_reply textarea')\n    reply_body.focus()\n  })\n}\n\nconst setReplyTo = id => {\n  $('input[name=\"reply[parent_id]\"]').val(id)\n  const replyEl = $(`.reply[data-id=${id}]`)\n  const targetAnchor = replyEl.attr('id')\n  const replyToPanel = $('.editor-toolbar .reply-to')\n  const userNameEl = replyEl.find('a.user-name:first-child')\n  const replyToLink = replyToPanel.find('.user')\n\n  replyToLink.attr('href', `#${targetAnchor}`)\n  replyToLink.text(userNameEl.text())\n  replyToPanel.show()\n}\n\nconst hookMention = () => {\n  $('textarea').atwho({\n    at: '@',\n    limit: 8,\n    searchKey: 'login',\n    callbacks: {\n      filter: function(query, data, searchKey) {\n        return data\n      },\n      sorter: function(query, items, searchKey) {\n        return items\n      },\n      remoteFilter: function(query, callback) {\n        $.getJSON(\n          '/search/users',\n          {\n            q: query\n          },\n          function(data) {\n            callback(data)\n          }\n        )\n      }\n    },\n    displayTpl:\n      \"<li data-value='${login}'><img src='${avatar_url}' height='20' width='20'/> ${login} </li>\",\n    insertTpl: '@${login}'\n  })\n}\n\nconst reloadWhenNewReply = () => {\n  $('.notify-updated a').click(function() {\n    window.location.reload()\n    $('.notify-updated').hide()\n  })\n}\n\nconst Topic = {\n  selectorNode,\n  hookPreview,\n  hookReply,\n  hookMention,\n  reloadWhenNewReply\n}\n\nexport default Topic\n"
  },
  {
    "path": "assets/js/app.js",
    "content": "import 'phoenix_html'\nimport 'bootstrap'\nimport 'jquery.caret'\nimport 'dropzone/dist/dropzone-amd-module'\n\nimport socket from './socket'\n\n// JS components\nimport Times from './app/components/times'\nimport Utils from './common/components/utils'\nimport Topic from './app/components/topic'\nimport Editor from './app/components/editor'\nimport Session from './app/components/session'\n\n// Decorate\nTimes.humanize()\nUtils.navActive()\n// Topic\nTopic.selectorNode()\nTopic.hookPreview($('.editor-toolbar'), $('.topic-editor'))\nTopic.hookReply()\nTopic.hookMention()\nTopic.reloadWhenNewReply()\n// Editor\nwindow._editor = new Editor()\n// Refresh Captcha\nSession.refreshExcaptcha()\n"
  },
  {
    "path": "assets/js/common/components/utils.js",
    "content": "export default class Utils {\n  static navActive() {\n    const url =\n      window.location.protocol +\n      '//' +\n      window.location.host +\n      window.location.pathname\n\n    $('.mi-nav li a')\n      .filter(function() {\n        return this.href == url\n      })\n      .closest('a')\n      .addClass('active')\n  }\n}\n"
  },
  {
    "path": "assets/js/socket.js",
    "content": "// NOTE: The contents of this file will only be executed if\n// you uncomment its entry in \"assets/js/app.js\".\n\n// To use Phoenix channels, the first step is to import Socket\n// and connect at the socket path in \"lib/web/endpoint.ex\":\nimport { Socket, Presence } from 'phoenix'\n\nlet socket = new Socket('/socket', { params: { token: window.userToken } })\n\nsocket.connect()\n\n// Now that you are connected, you can join channels with a topic:\nlet channel = socket.channel('room:lobby', {})\nchannel\n  .join()\n  .receive('ok', resp => {\n    console.log('Joined successfully', resp)\n  })\n  .receive('error', resp => {\n    console.log('Unable to join', resp)\n  })\n\n// Presences\nlet presences = {}\nchannel.on('presence_state', state => {\n  presences = Presence.syncState(presences, state)\n  renderOnlineUsers(presences)\n})\n\nchannel.on('presence_diff', diff => {\n  presences = Presence.syncDiff(presences, diff)\n  renderOnlineUsers(presences)\n})\n\nconst renderOnlineUsers = function(presences) {\n  let onlineUsers = `<strong>${\n    Object.keys(presences).length\n  } Online Users</strong>`\n  document.querySelector('#online-users').innerHTML = onlineUsers\n}\n\nlet channelTopicId = window.channelTopicId\n\nif (channelTopicId) {\n  let channel = socket.channel(`topic:${channelTopicId}`, {})\n  channel\n    .join()\n    .receive('ok', resp => {\n      console.log('Joined topic successfully', resp)\n    })\n    .receive('error', resp => {\n      console.log('Unable to join', resp)\n    })\n\n  channel.on(`topic:${channelTopicId}:new_reply`, message => {\n    if (message) {\n      $('.notify-updated').show()\n    }\n  })\n}\n\nexport default socket\n"
  },
  {
    "path": "assets/package.json",
    "content": "{\n  \"repository\": {},\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">=9.0\",\n    \"yarn\": \">=1.6.0\"\n  },\n  \"scripts\": {\n    \"deploy\": \"webpack --mode production\",\n    \"watch\": \"webpack --mode development --watch\",\n    \"lint\": \"eslint --ext .js js\",\n    \"prettier\": \"prettier 'js/**/*js' --write\",\n    \"precommit\": \"lint-staged\"\n  },\n  \"prettier\": {\n    \"printWidth\": 80,\n    \"semi\": false,\n    \"singleQuote\": true,\n    \"trailingComma\": \"none\",\n    \"bracketSpacing\": true\n  },\n  \"lint-staged\": {\n    \"js/**/*.js\": \"eslint\",\n    \"*.{js,json,css,md}\": [\n      \"prettier --write\",\n      \"git add\"\n    ]\n  },\n  \"dependencies\": {\n    \"bootstrap\": \"^4.3.1\",\n    \"dropzone\": \"^5.5.1\",\n    \"font-awesome\": \"^4.7.0\",\n    \"jquery\": \"^3.4.1\",\n    \"jquery.caret\": \"^0.3.1\",\n    \"lodash\": \"^4.17.13\",\n    \"moment\": \"^2.22.2\",\n    \"phoenix\": \"file:../deps/phoenix\",\n    \"phoenix_html\": \"file:../deps/phoenix_html\",\n    \"popper.js\": \"^1.14.3\"\n  },\n  \"devDependencies\": {\n    \"babel-core\": \"^6.26.0\",\n    \"babel-eslint\": \"^10.0.2\",\n    \"babel-loader\": \"^7.1.3\",\n    \"babel-preset-env\": \"^1.6.1\",\n    \"copy-webpack-plugin\": \"^5.1.1\",\n    \"css-loader\": \"^3.0.0\",\n    \"eslint\": \"^6.8.0\",\n    \"eslint-config-prettier\": \"^2.9.0\",\n    \"file-loader\": \"^1.1.11\",\n    \"husky\": \"^0.14.3\",\n    \"lint-staged\": \"^7.2.0\",\n    \"mini-css-extract-plugin\": \"^0.4.0\",\n    \"node-sass\": \"^4.12.0\",\n    \"optimize-css-assets-webpack-plugin\": \"^5.0.1\",\n    \"prettier\": \"^1.13.7\",\n    \"sass-loader\": \"^7.0.1\",\n    \"style-loader\": \"^0.20.2\",\n    \"uglifyjs-webpack-plugin\": \"^1.2.4\",\n    \"webpack\": \"^4.42.0\",\n    \"webpack-cli\": \"^3.3.4\"\n  }\n}\n"
  },
  {
    "path": "assets/static/robots.txt",
    "content": "# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n#\n# To ban all spiders from the entire site uncomment the next two lines:\n# User-agent: *\n# Disallow: /\n"
  },
  {
    "path": "assets/webpack.config.js",
    "content": "const path = require('path')\nconst webpack = require('webpack')\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin')\nconst UglifyJsPlugin = require('uglifyjs-webpack-plugin')\nconst OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst assets = path.normalize(__dirname)\n\nmodule.exports = (env, options) => ({\n  optimization: {\n    minimizer: [\n      new UglifyJsPlugin({\n        cache: true,\n        parallel: true,\n        sourceMap: false\n      }),\n      new OptimizeCSSAssetsPlugin({})\n    ]\n  },\n  entry: {\n    app: ['./js/app.js', './css/app.scss'],\n    admin: ['./js/admin.js', './css/admin.scss']\n  },\n  output: {\n    filename: '[name].js',\n    path: path.resolve(__dirname, '../priv/static/js')\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        exclude: /node_modules/,\n        use: {\n          loader: 'babel-loader'\n        }\n      },\n      {\n        test: /\\.s?[ac]ss$/,\n        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']\n      },\n      {\n        test: /.(ttf|otf|eot|svg|woff(2)?)(\\?[a-z0-9]+)?$/,\n        use: [\n          {\n            loader: 'file-loader',\n            options: {\n              name: '[name].[ext]',\n              outputPath: '../css/fonts/' // where the fonts will go\n            }\n          }\n        ]\n      }\n    ]\n  },\n  plugins: [\n    new MiniCssExtractPlugin({\n      filename: '../css/[name].css'\n    }),\n    new CopyWebpackPlugin([\n      {\n        from: 'static/',\n        to: '../'\n      }\n    ]),\n    new webpack.ProvidePlugin({\n      $: 'jquery',\n      jQuery: 'jquery',\n      'window.jQuery': 'jquery'\n    })\n  ],\n  resolve: {\n    alias: {\n      static: `${assets}/static`\n    }\n  }\n})\n"
  },
  {
    "path": "compile",
    "content": "cd $phoenix_dir\nnpm --prefix ./assets run deploy\nmix \"${phoenix_ex}.digest\"\n"
  },
  {
    "path": "config/config.exs",
    "content": "# This file is responsible for configuring your application\n# and its dependencies with the aid of the Mix.Config module.\n#\n# This configuration file is loaded before any dependency and\n# is restricted to this project.\nuse Mix.Config\n\n# General application configuration\nconfig :mipha,\n  ecto_repos: [Mipha.Repo]\n\n# Configures the endpoint\nconfig :mipha, MiphaWeb.Endpoint,\n  url: [host: \"localhost\"],\n  secret_key_base: \"Gbwl1EqAtjdYfG5movSWszQHvrppYxDbg/7xegRJSakWiTTl6ypdMpNgNct3LiDx\",\n  render_errors: [view: MiphaWeb.ErrorView, accepts: ~w(html json)],\n  pubsub_server: Mipha.PubSub\n  # pubsub: [name: Mipha.PubSub,\n  #          adapter: Phoenix.PubSub.PG2]\n\n# Configures Elixir's Logger\nconfig :logger, :console,\n  format: \"$time $metadata[$level] $message\\n\",\n  metadata: [:user_id]\n\nconfig :ueberauth, Ueberauth,\n  providers: [\n    github: {Ueberauth.Strategy.Github, [default_scope: \"user:email\", allow_private_emails: true]},\n    identity: {Ueberauth.Strategy.Identity, [callback_methods: [\"POST\"]]},\n  ]\n\nconfig :ueberauth, Ueberauth.Strategy.Github.OAuth,\n  client_id: System.get_env(\"GITHUB_CLIENT_ID\"),\n  client_secret: System.get_env(\"GITHUB_CLIENT_SECRET\")\n\nconfig :qiniu, Qiniu,\n  access_key: System.get_env(\"QINIU_ACCESS_KEY\"),\n  secret_key: System.get_env(\"QINIU_SECRET_KEY\")\n\nconfig :mipha, Mipha.Mailer,\n  adapter: Bamboo.LocalAdapter\n\n# Sentry\nconfig :sentry,\n  dsn: System.get_env(\"SENRTY_DSN\"),\n  included_environments: [:prod],\n  environment_name: Mix.env(),\n  enable_source_code_context: true,\n  root_source_code_path: File.cwd!()\n\nconfig :turbo_ecto, Turbo.Ecto,\n  repo: Mipha.Repo\n\n\nconfig :phoenix, :json_library, Jason\n\nconfig :gettext, :default_locale, \"zh\"\n\n# git_hooks\nif Mix.env() != :prod do\n  config :git_hooks,\n    auto_install: true,\n    verbose: true,\n    hooks: [\n      pre_commit: [\n        tasks: [\n          {:cmd, \"mix format\"},\n          {:cmd, \"mix credo --strict\"}\n        ]\n      ],\n      pre_push: [\n        verbose: false,\n        tasks: [\n          {:cmd, \"mix test\"},\n          {:cmd, \"echo 'success!'\"}\n        ]\n      ]\n    ]\nend\n\n# Import environment specific config. This must remain at the bottom\n# of this file so it overrides the configuration defined above.\nimport_config \"#{Mix.env}.exs\"\n"
  },
  {
    "path": "config/dev.exs",
    "content": "use Mix.Config\n\n# For development, we disable any cache and enable\n# debugging and code reloading.\n#\n# The watchers configuration can be used to run external\n# watchers to your application. For example, we use it\n# with brunch.io to recompile .js and .css sources.\nconfig :mipha, MiphaWeb.Endpoint,\n  http: [port: 4000],\n  debug_errors: true,\n  code_reloader: true,\n  check_origin: false,\n  watchers: [node: [\"node_modules/webpack/bin/webpack.js\", \"--mode\", \"development\", \"--watch-stdin\",\n                      cd: Path.expand(\"../assets\", __DIR__)]]\n\n# ## SSL Support\n#\n# In order to use HTTPS in development, a self-signed\n# certificate can be generated by running the following\n# command from your terminal:\n#\n#     openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj \"/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com\" -keyout priv/server.key -out priv/server.pem\n#\n# The `http:` config above can be replaced with:\n#\n#     https: [port: 4000, keyfile: \"priv/server.key\", certfile: \"priv/server.pem\"],\n#\n# If desired, both `http:` and `https:` keys can be\n# configured to run both http and https servers on\n# different ports.\n\n# Watch static and templates for browser reloading.\nconfig :mipha, MiphaWeb.Endpoint,\n  live_reload: [\n    patterns: [\n      ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},\n      ~r{priv/gettext/.*(po)$},\n      ~r{lib/mipha_web/views/.*(ex)$},\n      ~r{lib/mipha_web/templates/.*(eex)$}\n    ]\n  ]\n\n# Do not include metadata nor timestamps in development logs\nconfig :logger, :console, format: \"[$level] $message\\n\"\n\n# Set a higher stacktrace during development. Avoid configuring such\n# in production as building large stacktraces may be expensive.\nconfig :phoenix, :stacktrace_depth, 20\n\n# Configure your database\nconfig :mipha, Mipha.Repo,\n  username: \"postgres\",\n  password: \"postgres\",\n  database: \"mipha_dev\",\n  hostname: \"localhost\",\n  pool_size: 10\n"
  },
  {
    "path": "config/prod.exs",
    "content": "use Mix.Config\n\n# For production, we often load configuration from external\n# sources, such as your system environment. For this reason,\n# you won't find the :http configuration below, but set inside\n# MiphaWeb.Endpoint.init/2 when load_from_system_env is\n# true. Any dynamic configuration should be done there.\n#\n# Don't forget to configure the url host to something meaningful,\n# Phoenix uses this information when generating URLs.\n#\n# Finally, we also include the path to a cache manifest\n# containing the digested version of static files. This\n# manifest is generated by the mix phx.digest task\n# which you typically run after static files are built.\nconfig :mipha, MiphaWeb.Endpoint,\n  load_from_system_env: true,\n  url: [scheme: \"https\", host: \"elixir-mipha.herokuapp.com\", port: 443],\n  cache_static_manifest: \"priv/static/cache_manifest.json\",\n  secret_key_base: Map.fetch!(System.get_env(), \"SECRET_KEY_BASE\")\n\n# Do not print debug messages in production\nconfig :logger, level: :info\n\nconfig :mipha, Mipha.Mailer,\n  adapter: Bamboo.SMTPAdapter,\n  port: 465,\n  server: \"smtp.gmail.com\",\n  username: System.get_env(\"GMAIL_USERNAME\"),\n  password: System.get_env(\"GMAIL_PASSWORD\"),\n  tls: :never, # can be `:always` or `:never` or `:if_available`\n  ssl: true,\n  retries: 3\n\n# ## SSL Support\n#\n# To get SSL working, you will need to add the `https` key\n# to the previous section and set your `:url` port to 443:\n#\n#     config :mipha, MiphaWeb.Endpoint,\n#       ...\n#       url: [host: \"example.com\", port: 443],\n#       https: [:inet6,\n#               port: 443,\n#               keyfile: System.get_env(\"SOME_APP_SSL_KEY_PATH\"),\n#               certfile: System.get_env(\"SOME_APP_SSL_CERT_PATH\")]\n#\n# Where those two env variables return an absolute path to\n# the key and cert in disk or a relative path inside priv,\n# for example \"priv/ssl/server.key\".\n#\n# We also recommend setting `force_ssl`, ensuring no data is\n# ever sent via http, always redirecting to https:\n#\n#     config :mipha, MiphaWeb.Endpoint,\n#       force_ssl: [hsts: true]\n#\n# Check `Plug.SSL` for all available options in `force_ssl`.\n\n# ## Using releases\n#\n# If you are doing OTP releases, you need to instruct Phoenix\n# to start the server for all endpoints:\n#\n#     config :phoenix, :serve_endpoints, true\n#\n# Alternatively, you can configure exactly which server to\n# start per endpoint:\n#\n#     config :mipha, MiphaWeb.Endpoint, server: true\n#\n\n# Finally import the config/prod.secret.exs\n# which should be versioned separately.\n# import_config \"prod.secret.exs\"\nconfig :mipha, Mipha.Repo,\n  adapter: Ecto.Adapters.Postgres,\n  url: System.get_env(\"DATABASE_URL\"),\n  pool_size: String.to_integer(System.get_env(\"POOL_SIZE\") || \"10\"),\n  ssl: true\n"
  },
  {
    "path": "config/test.exs",
    "content": "use Mix.Config\n\n# We don't run a server during test. If one is required,\n# you can enable the server option below.\nconfig :mipha, MiphaWeb.Endpoint,\n  http: [port: 4001],\n  server: false\n\n# Print only warnings and errors during test\nconfig :logger, level: :warn\n\n# Configure your database\nconfig :mipha, Mipha.Repo,\n  adapter: Ecto.Adapters.Postgres,\n  username: \"postgres\",\n  password: \"postgres\",\n  database: \"mipha_test\",\n  hostname: System.get_env(\"DB_HOST\") || \"localhost\",\n  pool: Ecto.Adapters.SQL.Sandbox\n"
  },
  {
    "path": "docker/docker-compose.yml",
    "content": "version: \"3.6\"\n\n# cd docker && docker-compose up -d\n\nservices:\n  postgresql_dev:\n    image: sameersbn/postgresql:9.6\n    environment:\n      - PG_PASSWORD=postgres\n    ports:\n      - \"5432:5432\"\n"
  },
  {
    "path": "elixir_buildpack.config",
    "content": "# Elixir version\nelixir_version=1.8.1\n\n# Erlang version\n# available versions https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions\nerlang_version=21.2.5"
  },
  {
    "path": "lib/mipha/accounts/accounts.ex",
    "content": "defmodule Mipha.Accounts do\n  @moduledoc \"\"\"\n  The Accounts context.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n\n  alias Comeonin.Bcrypt\n  alias HTTPoison\n  alias Mipha.Repo\n  alias Mipha.Accounts.{User, Team}\n  alias Mipha.Utils.Store\n  alias Mipha.Follows.Follow\n\n  @doc \"\"\"\n  Gets a single user.\n\n  Raises `Ecto.NoResultsError` if the User does not exist.\n\n  ## Examples\n\n      iex> get_user!(123)\n      %User{}\n\n      iex> get_user!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_user!(id), do: Repo.get!(User, id)\n\n  def get_user(id), do: Repo.get(User, id)\n\n  @doc \"\"\"\n  Creates a user.\n\n  ## Examples\n\n      iex> create_user(%{field: value})\n      {:ok, %User{}}\n\n      iex> create_user(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_user(attrs \\\\ %{}) do\n    %User{}\n    |> User.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a user.\n\n  ## Examples\n\n      iex> update_user(user, %{field: new_value})\n      {:ok, %User{}}\n\n      iex> update_user(user, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_user(%User{} = user, attrs) do\n    user\n    |> User.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a User.\n\n  ## Examples\n\n      iex> delete_user(user)\n      {:ok, %User{}}\n\n      iex> delete_user(user)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_user(%User{} = user) do\n    Repo.delete(user)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking user changes.\n\n  ## Examples\n\n      iex> change_user(user)\n      %Ecto.Changeset{source: %User{}}\n\n  \"\"\"\n  def change_user(%User{} = user) do\n    User.changeset(user, %{})\n  end\n\n  @doc \"\"\"\n  Register user.\n\n  ## Example\n\n      iex> register_user(%{username: \"user\", email:\"user@minpha.com\", password: \"123123123\"})\n      {:ok, %User{}}\n\n      iex> register_user(%{username: \"user\", email:\"user@minpha\", password: \"123123123\"})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  @spec register_user(map()) :: {:ok, User.t()} | {:error, %Ecto.Changeset{}}\n  def register_user(attrs) do\n    %User{}\n    |> User.register_changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Authenticate user login.\n\n  ## Examples\n\n      iex> authenticate(%{login: \"zven\", password: \"123123123\"})\n      {:ok, %User{}}\n\n      iex> authenticate(%{login: \"zven\", password: \"123123123\"})\n      {:error, \"Incorrect login credentials\"}\n\n  \"\"\"\n  @spec authenticate(map()) :: {:ok, User.t()} | {:error, String.t()}\n  def authenticate(attrs) do\n    user = get_user_by_email(attrs.login) || get_user_by_username(attrs.login)\n\n    case check_user_password(user, attrs.password) do\n      true -> {:ok, user}\n      _ -> {:error, \"Failed auth.\"}\n    end\n  end\n\n  @doc \"\"\"\n  Gets a user by given clauses.\n  \"\"\"\n  @spec get_user_by(Keyword.t(), Keyword.t()) :: User.t() | nil\n  def get_user_by(clauses, _opts \\\\ []) do\n    User\n    |> preload([:location, :company, :teams])\n    |> Repo.get_by(clauses)\n  end\n\n  @doc \"\"\"\n  Gets a user by its username.\n\n  ## Examples\n\n      iex> get_user_by_username(\"user123\")\n      %User{}\n\n      iex> get_user_by_username(\"user456\")\n      nil\n\n  \"\"\"\n  @spec get_user_by_username(String.t(), Keyword.t()) :: User.t() | nil\n  def get_user_by_username(username, opts \\\\ []),\n    do: get_user_by([username: username], opts)\n\n  @doc \"\"\"\n  Gets a user by its email.\n\n  ## Examples\n\n      iex> get_user_by_email(\"user123@mipha.com\")\n      %User{}\n\n      iex> get_user_by_email(\"user456@mipha.com\")\n      nil\n\n  \"\"\"\n  @spec get_user_by_email(String.t(), Keyword.t()) :: User.t() | nil\n  def get_user_by_email(email, opts \\\\ []),\n    do: get_user_by([email: email], opts)\n\n  defp check_user_password(user, password) do\n    case user do\n      nil -> false\n      _ -> !is_nil(user.password_hash) && Bcrypt.checkpw(password, user.password_hash)\n    end\n  end\n\n  @doc \"\"\"\n  Gets the user count.\n\n  ## Examples\n\n      iex> get_user_count\n      40\n\n  \"\"\"\n  @spec get_user_count :: non_neg_integer()\n  def get_user_count do\n    User\n    |> Repo.aggregate(:count, :id)\n  end\n\n  @doc \"\"\"\n  获取全部 user 个数\n  \"\"\"\n  @spec get_total_user_count :: non_neg_integer()\n  def get_total_user_count do\n    User\n    |> Repo.aggregate(:count, :id)\n  end\n\n  @doc \"\"\"\n  Updates user password.\n  \"\"\"\n  @spec update_user_password(User.t(), map()) :: {:ok, User.t()} | {:error, any()}\n  def update_user_password(user, attrs) do\n    case check_user_password(user, attrs[\"current_password\"]) do\n      true ->\n        user\n        |> User.update_password_changeset(attrs)\n        |> Repo.update()\n\n      _ ->\n        {:error, \"Invalid current password\"}\n    end\n  end\n\n  @doc \"\"\"\n  Mark the current user verified\n  \"\"\"\n  def mark_as_verified(user) do\n    attrs = %{\"email_verified_at\" => Timex.now()}\n    update_user(user, attrs)\n  end\n\n  @doc \"\"\"\n  找回密码-重置密码\n  \"\"\"\n  def update_reset_password(user, attrs) do\n    user\n    |> User.reset_password_changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Github 登录应对。\n  \"\"\"\n  def login_or_register_from_github(%{nickname: nickname, name: nil, email: _email} = user) do\n    login_or_register_from_github(%{user | name: nickname})\n  end\n\n  def login_or_register_from_github(%{nickname: nickname, name: _name, email: nil} = user) do\n    login_or_register_from_github(%{user | email: nickname <> \"@users.noreply.github.com\"})\n  end\n\n  def login_or_register_from_github(%{nickname: nickname, name: _name, email: email}) do\n    case get_user_by_username(nickname) do\n      nil ->\n        create_user(%{\n          email: email,\n          username: nickname\n        })\n\n      user ->\n        {:ok, user}\n    end\n  end\n\n  @doc \"\"\"\n  获取 用户或组织的 github repos，这里会借助缓存处理。\n\n  ## TODO\n\n      需要队列处理\n\n  ## Example\n\n      iex> github_repositories(user)\n      [%{}, %{}]\n\n      iex> github_repositories(user)\n      []\n\n  \"\"\"\n  def github_repositories(%User{} = user) do\n    user\n    |> github_repos_cache_key\n    |> Store.get!()\n    |> fetch_github_repos(user)\n  end\n\n  def github_repositories(%Team{} = team) do\n    team\n    |> github_repos_cache_key\n    |> Store.get!()\n    |> fetch_github_repos(team)\n  end\n\n  # 获取 github repos 信息\n  defp fetch_github_repos(items, target) when is_nil(items) do\n    repos =\n      target\n      |> github_repos_url\n      |> HTTPoison.get!()\n      |> handle_response\n\n    Store.put!(github_repos_cache_key(target), repos)\n    repos\n  end\n\n  defp fetch_github_repos(items, _) do\n    items\n  end\n\n  # 拉取数据，并且 Json 处理\n  defp handle_response(%HTTPoison.Response{body: body, status_code: 200}) do\n    body\n    |> Jason.decode!()\n    |> Enum.map(&Map.take(&1, ~w(name html_url watchers language description)))\n    |> Enum.sort(&(&1[\"watchers\"] >= &2[\"watchers\"]))\n    |> Enum.take(10)\n  end\n\n  defp handle_response(%HTTPoison.Response{body: _, status_code: 404}) do\n    []\n  end\n\n  # 获取缓存 cache_key\n  defp github_repos_cache_key(target) do\n    \"github-repos:\" <> github_handle(target)\n  end\n\n  # 请求获取 github 用户的 repos 的 Url\n  defp github_repos_url(target) do\n    \"https://api.github.com/users/#{github_handle(target)}/repos?type=owner&sort=pushed&client_id=#{\n      System.get_env(\"GITHUB_CLIENT_ID\")\n    }&client_secret=#{System.get_env(\"GITHUB_CLIENT_SECRET\")}\"\n  end\n\n  @doc \"\"\"\n  获取 user 或 team 的 github 账号。\n  \"\"\"\n  def github_handle(%User{} = user) do\n    user.github_handle || user.username\n  end\n\n  def github_handle(%Team{} = team) do\n    team.github_handle || team.name\n  end\n\n  @doc \"\"\"\n  添加 帖子与评论内容时，mention user.\n  \"\"\"\n  @spec search_mention_user(User.t(), String.t()) :: any\n  def search_mention_user(%User{} = user, q) do\n    query =\n      from u in User,\n        join: f in Follow,\n        on: f.user_id == u.id,\n        where: f.follower_id == ^user.id,\n        where: like(u.username, ^\"#{q}%\")\n\n    query\n    |> Repo.all()\n    |> Enum.uniq()\n  end\n\n  @doc \"\"\"\n  Returns the user register changeset.\n  \"\"\"\n  @spec user_register_changeset(Map.t()) :: Ecto.Changeset.t()\n  def user_register_changeset(attrs \\\\ %{}), do: User.register_changeset(%User{}, attrs)\n\n  @doc \"\"\"\n  Returns the user login changeset.\n  \"\"\"\n  @spec user_login_changeset(Map.t()) :: Ecto.Changeset.t()\n  def user_login_changeset(attrs \\\\ %{}), do: User.login_changeset(%User{}, attrs)\n\n  @doc \"\"\"\n  Returns the user update_password changeset\n  \"\"\"\n  @spec user_update_password_changeset(Map.t()) :: Ecto.Changeset.t()\n  def user_update_password_changeset(attrs \\\\ %{}),\n    do: User.update_password_changeset(%User{}, attrs)\n\n  @doc \"\"\"\n  Returns the change user reset password.\n  \"\"\"\n  @spec change_user_reset_password(User.t(), Map.t()) :: Ecto.Changeset.t()\n  def change_user_reset_password(%User{} = user, attrs \\\\ %{}),\n    do: User.reset_password_changeset(user, attrs)\n\n  alias Mipha.Accounts.Location\n\n  @doc \"\"\"\n  Returns the list of locations.\n\n  ## Examples\n\n      iex> list_locations()\n      [%Location{}, ...]\n\n  \"\"\"\n  def list_locations do\n    Repo.all(Location)\n  end\n\n  @doc \"\"\"\n  Gets a single location.\n\n  Raises `Ecto.NoResultsError` if the Location does not exist.\n\n  ## Examples\n\n      iex> get_location!(123)\n      %Location{}\n\n      iex> get_location!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_location!(id) do\n    Location\n    |> Repo.get!(id)\n    |> Repo.preload([:users])\n  end\n\n  @doc \"\"\"\n  Creates a location.\n\n  ## Examples\n\n      iex> create_location(%{field: value})\n      {:ok, %Location{}}\n\n      iex> create_location(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_location(attrs \\\\ %{}) do\n    %Location{}\n    |> Location.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a location.\n\n  ## Examples\n\n      iex> update_location(location, %{field: new_value})\n      {:ok, %Location{}}\n\n      iex> update_location(location, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_location(%Location{} = location, attrs) do\n    location\n    |> Location.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Location.\n\n  ## Examples\n\n      iex> delete_location(location)\n      {:ok, %Location{}}\n\n      iex> delete_location(location)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_location(%Location{} = location) do\n    Repo.delete(location)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking location changes.\n\n  ## Examples\n\n      iex> change_location(location)\n      %Ecto.Changeset{source: %Location{}}\n\n  \"\"\"\n  def change_location(%Location{} = location) do\n    Location.changeset(location, %{})\n  end\n\n  alias Mipha.Accounts.Company\n\n  @doc \"\"\"\n  Gets a single company.\n\n  Raises `Ecto.NoResultsError` if the Company does not exist.\n\n  ## Examples\n\n      iex> get_company!(123)\n      %Company{}\n\n      iex> get_company!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_company!(id), do: Repo.get!(Company, id)\n\n  @doc \"\"\"\n  Creates a company.\n\n  ## Examples\n\n      iex> create_company(%{field: value})\n      {:ok, %Company{}}\n\n      iex> create_company(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_company(attrs \\\\ %{}) do\n    %Company{}\n    |> Company.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a company.\n\n  ## Examples\n\n      iex> update_company(company, %{field: new_value})\n      {:ok, %Company{}}\n\n      iex> update_company(company, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_company(%Company{} = company, attrs) do\n    company\n    |> Company.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Company.\n\n  ## Examples\n\n      iex> delete_company(company)\n      {:ok, %Company{}}\n\n      iex> delete_company(company)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_company(%Company{} = company) do\n    Repo.delete(company)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking company changes.\n\n  ## Examples\n\n      iex> change_company(company)\n      %Ecto.Changeset{source: %Company{}}\n\n  \"\"\"\n  def change_company(%Company{} = company) do\n    Company.changeset(company, %{})\n  end\n\n  @doc \"\"\"\n  Returns the list of teams.\n\n  ## Examples\n\n      iex> list_teams()\n      [%Team{}, ...]\n\n  \"\"\"\n  def list_teams do\n    Repo.all(Team)\n  end\n\n  @doc \"\"\"\n  Gets a single team.\n\n  Raises `Ecto.NoResultsError` if the Team does not exist.\n\n  ## Examples\n\n      iex> get_team!(123)\n      %Team{}\n\n      iex> get_team!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_team!(id) do\n    Team\n    |> Repo.get!(id)\n    |> Repo.preload([:users, :owner])\n  end\n\n  @doc \"\"\"\n  Creates a team.\n\n  ## Examples\n\n      iex> create_team(%{field: value})\n      {:ok, %Team{}}\n\n      iex> create_team(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_team(attrs \\\\ %{}) do\n    %Team{}\n    |> Team.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a team.\n\n  ## Examples\n\n      iex> update_team(team, %{field: new_value})\n      {:ok, %Team{}}\n\n      iex> update_team(team, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_team(%Team{} = team, attrs) do\n    team\n    |> Team.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Team.\n\n  ## Examples\n\n      iex> delete_team(team)\n      {:ok, %Team{}}\n\n      iex> delete_team(team)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_team(%Team{} = team) do\n    Repo.delete(team)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking team changes.\n\n  ## Examples\n\n      iex> change_team(team)\n      %Ecto.Changeset{source: %Team{}}\n\n  \"\"\"\n  def change_team(%Team{} = team) do\n    Team.changeset(team, %{})\n  end\n\n  alias Mipha.Accounts.UserTeam\n\n  @doc \"\"\"\n  Returns the list of users_teams.\n\n  ## Examples\n\n      iex> list_users_teams()\n      [%UserTeam{}, ...]\n\n  \"\"\"\n  def list_users_teams do\n    Repo.all(UserTeam)\n  end\n\n  @doc \"\"\"\n  Gets a single user_team.\n\n  Raises `Ecto.NoResultsError` if the User team does not exist.\n\n  ## Examples\n\n      iex> get_user_team!(123)\n      %UserTeam{}\n\n      iex> get_user_team!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_user_team!(id), do: Repo.get!(UserTeam, id)\n\n  @doc \"\"\"\n  Creates a user_team.\n\n  ## Examples\n\n      iex> create_user_team(%{field: value})\n      {:ok, %UserTeam{}}\n\n      iex> create_user_team(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_user_team(attrs \\\\ %{}) do\n    %UserTeam{}\n    |> UserTeam.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a user_team.\n\n  ## Examples\n\n      iex> update_user_team(user_team, %{field: new_value})\n      {:ok, %UserTeam{}}\n\n      iex> update_user_team(user_team, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_user_team(%UserTeam{} = user_team, attrs) do\n    user_team\n    |> UserTeam.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a UserTeam.\n\n  ## Examples\n\n      iex> delete_user_team(user_team)\n      {:ok, %UserTeam{}}\n\n      iex> delete_user_team(user_team)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_user_team(%UserTeam{} = user_team) do\n    Repo.delete(user_team)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking user_team changes.\n\n  ## Examples\n\n      iex> change_user_team(user_team)\n      %Ecto.Changeset{source: %UserTeam{}}\n\n  \"\"\"\n  def change_user_team(%UserTeam{} = user_team) do\n    UserTeam.changeset(user_team, %{})\n  end\nend\n"
  },
  {
    "path": "lib/mipha/accounts/company.ex",
    "content": "defmodule Mipha.Accounts.Company do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Mipha.Accounts.{User, Location, Company}\n\n  @type t :: %Company{}\n\n  schema \"companies\" do\n    field :name, :string\n    field :avatar, :string\n\n    belongs_to :location, Location\n\n    has_many :users, User\n\n    timestamps()\n  end\n\n  @doc false\n  def changeset(company, attrs) do\n    permitted_attrs = ~w(\n      name\n      avatar\n      location_id\n    )a\n\n    required_attrs = ~w(\n      name\n      location_id\n    )a\n\n    company\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/accounts/location.ex",
    "content": "defmodule Mipha.Accounts.Location do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  alias Mipha.Accounts.{User, Location, Company}\n\n  @type t :: %Location{}\n\n  schema \"locations\" do\n    field :name, :string\n\n    has_many :users, User\n    has_many :companies, Company\n\n    timestamps()\n  end\n\n  @doc false\n  def changeset(location, attrs) do\n    location\n    |> cast(attrs, [:name])\n    |> validate_required([:name])\n  end\nend\n"
  },
  {
    "path": "lib/mipha/accounts/queries.ex",
    "content": "defmodule Mipha.Accounts.Queries do\n  @moduledoc false\n\n  alias Mipha.Accounts.{User, Team, Company}\n  import Ecto.Query\n\n  @doc \"\"\"\n  Returns the list users.\n  \"\"\"\n  @spec list_users :: Ecto.Query.t()\n  def list_users, do: User |> order_by([q], asc: q.inserted_at)\n\n  @doc \"\"\"\n  Returns the list teams.\n  \"\"\"\n  @spec list_teams :: Ecto.Query.t()\n  def list_teams, do: Team\n\n  @doc \"\"\"\n  Returns the list companies.\n  \"\"\"\n  @spec list_companies :: Ecto.Query.t()\n  def list_companies, do: Company\nend\n"
  },
  {
    "path": "lib/mipha/accounts/team.ex",
    "content": "defmodule Mipha.Accounts.Team do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Mipha.Accounts.{User, Team, UserTeam}\n\n  @type t :: %Team{}\n\n  schema \"teams\" do\n    field :name, :string\n    field :slug, :string\n    field :avatar, :string\n    field :summary, :string\n    field :website, :string\n    field :github_handle, :string\n    field :twitter_handle, :string\n    field :email, :string\n    field :email_public, :boolean\n\n    belongs_to :owner, User\n\n    many_to_many :users, User, join_through: UserTeam\n\n    timestamps()\n  end\n\n  @doc false\n  def changeset(team, attrs) do\n    team\n    |> cast(attrs, [:owner_id, :github_handle, :name, :summary, :avatar])\n    |> validate_required([:owner_id, :github_handle, :name, :summary, :avatar])\n  end\nend\n"
  },
  {
    "path": "lib/mipha/accounts/user.ex",
    "content": "defmodule Mipha.Accounts.User do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Comeonin.Bcrypt\n\n  alias Mipha.{\n    Regexp,\n    Topics.Topic,\n    Replies.Reply,\n    Follows.Follow,\n    Stars.Star,\n    Collections.Collection\n  }\n\n  alias Mipha.Notifications.{Notification, UserNotification}\n  alias Mipha.Accounts.{User, Location, Company, Team, UserTeam}\n\n  @type t :: %User{}\n\n  schema \"users\" do\n    field :username, :string\n    field :avatar, :string\n    field :bio, :string\n    field :email, :string\n    field :email_public, :boolean, default: false\n    field :email_verified_at, :naive_datetime\n    field :tagline, :string\n    field :github_handle, :string\n    field :is_admin, :boolean, default: false\n    field :password_hash, :string, default: \"\"\n    field :website, :string\n    field :alipay, :string\n    field :wechat, :string\n    field :locked_at, :naive_datetime\n    field :password, :string, virtual: true\n    field :password_confirmation, :string, virtual: true\n    field :login, :string, virtual: true\n    field :reset_password_token, :string, virtual: true\n\n    belongs_to :location, Location\n    belongs_to :company, Company\n\n    has_many :topics, Topic, on_delete: :delete_all\n    has_many :replies, Reply, on_delete: :delete_all\n    has_many :followers, Follow, on_delete: :delete_all\n    has_many :following, Follow, foreign_key: :follower_id\n    has_many :stars, Star, on_delete: :delete_all\n    has_many :collections, Collection, on_delete: :delete_all\n\n    many_to_many :teams, Team, join_through: UserTeam\n    many_to_many :notifications, Notification, join_through: UserNotification\n\n    timestamps()\n  end\n\n  @doc false\n  def changeset(user, attrs) do\n    permitted_attrs = ~w(\n      username\n      email\n      avatar\n      bio\n      website\n      github_handle\n      is_admin\n      location_id\n      company_id\n      tagline\n      email_public\n      wechat\n      alipay\n      email_verified_at\n    )a\n\n    required_attrs = ~w(\n      username\n      email\n    )a\n\n    user\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n    |> assoc_constraint(:location)\n    |> assoc_constraint(:company)\n  end\n\n  @doc \"\"\"\n  User login changeset.\n  \"\"\"\n  def login_changeset(user, attrs) do\n    login_attrs = ~w(\n      login\n      password\n    )a\n\n    user\n    |> cast(attrs, login_attrs)\n    |> validate_required(login_attrs)\n  end\n\n  @doc \"\"\"\n  Registration changeset.\n  \"\"\"\n  def register_changeset(user, attrs) do\n    permitted_attrs = ~w(\n      username\n      email\n      password\n      avatar\n      is_admin\n      bio\n      website\n      github_handle\n      company_id\n      location_id\n    )a\n\n    register_attrs = ~w(\n      username\n      email\n      password\n    )a\n\n    user\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(register_attrs)\n    |> validate_length(:username, min: 3, max: 12)\n    |> validate_format(:username, Regexp.username())\n    |> unique_constraint(:username)\n    |> validate_length(:email, min: 1, max: 20)\n    |> validate_format(:email, Regexp.email())\n    |> unique_constraint(:email)\n    |> put_pass_hash()\n  end\n\n  @doc \"\"\"\n  Change user password.\n  \"\"\"\n  @spec update_password_changeset(User.t(), map()) :: Ecto.Changeset.t()\n  def update_password_changeset(user, attrs) do\n    required_attrs = ~w(\n      password\n      password_confirmation\n    )a\n\n    user\n    |> cast(attrs, required_attrs)\n    |> validate_required(required_attrs)\n    |> validate_password_confirmation\n    |> put_pass_hash\n  end\n\n  # def forgot_password_changeset(user, attrs) do\n  #   required_attrs = ~w(email)a\n\n  #   user\n  #   |> cast(attrs, required_attrs)\n  #   |> validate_required(required_attrs)\n  # end\n\n  @doc \"\"\"\n  Reset Password when user forgot password.\n  \"\"\"\n  @spec reset_password_changeset(User.t(), map()) :: Ecto.Changeset.t()\n  def reset_password_changeset(user, attrs) do\n    required_attrs = ~w(\n      reset_password_token\n      password\n      password_confirmation\n    )a\n\n    user\n    |> cast(attrs, required_attrs)\n    |> validate_required(required_attrs)\n    |> validate_password_confirmation\n    |> put_pass_hash\n  end\n\n  defp validate_password_confirmation(%{changes: changes} = changeset) do\n    if changes[:password] == changes[:password_confirmation],\n      do: changeset,\n      else: add_error(changeset, :password_confirmation, \"must match password\")\n  end\n\n  defp put_pass_hash(changeset) do\n    case changeset do\n      %Ecto.Changeset{valid?: true, changes: %{password: password}} ->\n        put_change(changeset, :password_hash, Bcrypt.hashpwsalt(password))\n\n      _ ->\n        changeset\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha/accounts/user_team.ex",
    "content": "defmodule Mipha.Accounts.UserTeam do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias Mipha.Accounts.{User, Team}\n\n  schema \"users_teams\" do\n    field :role, :string\n    field :status, :string\n\n    belongs_to :team, Team\n    belongs_to :user, User\n\n    timestamps()\n  end\n\n  @doc false\n  def changeset(user_team, attrs) do\n    user_team\n    |> cast(attrs, [:user_id, :team_id])\n    |> validate_required([:user_id, :team_id])\n  end\nend\n"
  },
  {
    "path": "lib/mipha/application.ex",
    "content": "defmodule Mipha.Application do\n  @moduledoc false\n\n  use Application\n\n  # See https://hexdocs.pm/elixir/Application.html\n  # for more information on OTP Applications\n  def start(_type, _args) do\n    import Supervisor.Spec\n\n    # Define workers and child supervisors to be supervised\n    children = [\n      # Start the Ecto repository\n      supervisor(Mipha.Repo, []),\n      {Phoenix.PubSub, name: Mipha.PubSub},\n      # Start the endpoint when the application starts\n      supervisor(MiphaWeb.Endpoint, []),\n      supervisor(MiphaWeb.Presence, []),\n      # Start your own worker by calling: Mipha.Worker.start_link(arg1, arg2, arg3)\n      worker(Cachex, [:app_cache, []]),\n      worker(PlugAttack.Storage.Ets, [Mipha.PlugAttack.Storage, [clean_period: 60_000]])\n      # worker(Mipha.Worker, [arg1, arg2, arg3]),\n    ]\n\n    # See https://hexdocs.pm/elixir/Supervisor.html\n    # for other strategies and supported options\n    opts = [strategy: :one_for_one, name: Mipha.Supervisor]\n    Supervisor.start_link(children, opts)\n  end\n\n  # Tell Phoenix to update the endpoint configuration\n  # whenever the application is updated.\n  def config_change(changed, _new, removed) do\n    MiphaWeb.Endpoint.config_change(changed, removed)\n    :ok\n  end\nend\n"
  },
  {
    "path": "lib/mipha/collections/collection.ex",
    "content": "defmodule Mipha.Collections.Collection do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.{Changeset, Query}\n\n  alias Mipha.{\n    Topics.Topic,\n    Accounts.User,\n    Collections.Collection\n  }\n\n  @type t :: %Collection{}\n\n  schema \"collections\" do\n    belongs_to :user, User\n    belongs_to :topic, Topic\n\n    timestamps()\n  end\n\n  @doc \"\"\"\n  Filters collection by a user.\n  \"\"\"\n  @spec by_user(Ecto.Queryable.t(), User.t()) :: Ecto.Query.t()\n  def by_user(query \\\\ __MODULE__, %User{id: user_id}),\n    do: where(query, [..., c], c.user_id == ^user_id)\n\n  @doc \"\"\"\n  Filters collection by a topic.\n  \"\"\"\n  @spec by_topic(Ecto.Queryable.t(), Topic.t()) :: Ecto.Query.t()\n  def by_topic(query \\\\ __MODULE__, %Topic{id: topic_id}),\n    do: where(query, [..., c], c.topic_id == ^topic_id)\n\n  @doc false\n  def changeset(collection, attrs) do\n    permitted_attrs = ~w(\n      user_id\n      topic_id\n    )a\n\n    required_attrs = ~w(\n      user_id\n      topic_id\n    )a\n\n    collection\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/collections/collections.ex",
    "content": "defmodule Mipha.Collections do\n  @moduledoc \"\"\"\n  The Collections context.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias Mipha.Repo\n\n  alias Mipha.Collections.Collection\n  alias Mipha.Accounts.User\n  alias Mipha.Topics.Topic\n\n  @doc \"\"\"\n  Returns the list of collections.\n\n  ## Examples\n\n      iex> list_collections()\n      [%Collection{}, ...]\n\n  \"\"\"\n  def list_collections do\n    Repo.all(Collection)\n  end\n\n  @doc \"\"\"\n  Gets a single collection.\n\n  Raises `Ecto.NoResultsError` if the Collection does not exist.\n\n  ## Examples\n\n      iex> get_collection!(123)\n      %Collection{}\n\n      iex> get_collection!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_collection!(id), do: Repo.get!(Collection, id)\n\n  @doc \"\"\"\n  Creates a collection.\n\n  ## Examples\n\n      iex> create_collection(%{field: value})\n      {:ok, %Collection{}}\n\n      iex> create_collection(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_collection(attrs \\\\ %{}) do\n    %Collection{}\n    |> Collection.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a collection.\n\n  ## Examples\n\n      iex> update_collection(collection, %{field: new_value})\n      {:ok, %Collection{}}\n\n      iex> update_collection(collection, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_collection(%Collection{} = collection, attrs) do\n    collection\n    |> Collection.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Collection.\n\n  ## Examples\n\n      iex> delete_collection(collection)\n      {:ok, %Collection{}}\n\n      iex> delete_collection(collection)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_collection(%Collection{} = collection) do\n    Repo.delete(collection)\n  end\n\n  @spec delete_collection(Keyword.t()) :: {:ok, Collection.t()} | nil\n  def delete_collection(clauses) when length(clauses) == 2 do\n    clauses\n    |> get_collection\n    |> Repo.delete()\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking collection changes.\n\n  ## Examples\n\n      iex> change_collection(collection)\n      %Ecto.Changeset{source: %Collection{}}\n\n  \"\"\"\n  def change_collection(%Collection{} = collection) do\n    Collection.changeset(collection, %{})\n  end\n\n  @doc \"\"\"\n  Gets the collection count of a user.\n\n  ## Examples\n\n    iex> get_collection_count(%User{})\n    42\n\n  \"\"\"\n  @spec get_collection_count(User.t()) :: non_neg_integer()\n  def get_collection_count(%User{} = user) do\n    user\n    |> Collection.by_user()\n    |> Repo.aggregate(:count, :id)\n  end\n\n  @doc \"\"\"\n  Gets a star.\n\n  ## Examples\n\n      iex> get_collection(user_id: 123, topic_id: 123)\n      %Star{}\n\n      iex> get_collection(user_id: 123, topic_id: 456)\n      nil\n\n  \"\"\"\n  @spec get_collection(Keyword.t()) :: Collection.t() | nil\n  def get_collection(clauses) when length(clauses) == 2, do: Repo.get_by(Collection, clauses)\n\n  @doc \"\"\"\n  Returns `true` if the user has starred the starrable.\n  `false` otherwise.\n\n  ## Examples\n\n      iex> has_collected?(user: %User{}, topic: %Topic{})\n      true\n\n      iex> has_collected?(user: %User{}, topic: %Topic{})\n      false\n\n  \"\"\"\n  @spec has_collected?(Keyword.t()) :: boolean\n  def has_collected?(clauses) do\n    %User{id: user_id} = Keyword.get(clauses, :user)\n    %Topic{id: topic_id} = Keyword.get(clauses, :topic)\n\n    opts = [user_id: user_id, topic_id: topic_id]\n    !!get_collection(opts)\n  end\n\n  @doc \"\"\"\n  Insert a collection.\n\n  ## Examples\n\n      iex> insert_collection(attrs)\n      {:ok, %Follow{}}\n\n      iex> insert_collection(attrs)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def insert_collection(attrs \\\\ %{}) do\n    %Collection{}\n    |> Collection.changeset(attrs)\n    |> Repo.insert()\n  end\nend\n"
  },
  {
    "path": "lib/mipha/collections/queries.ex",
    "content": "defmodule Mipha.Collections.Queries do\n  @moduledoc false\n\n  import Ecto.Query\n  alias Mipha.Collections.Collection\n\n  @doc \"\"\"\n  Filter the list of collections.\n\n  ## Examples\n\n      iex> cond_collections(params)\n      Ecto.Query.t()\n\n  \"\"\"\n  @spec cond_collections(Keyword.t()) :: Ecto.Query.t()\n  def cond_collections(opts \\\\ []) do\n    opts\n    |> filter_from_clauses\n    |> preload([:user, [topic: :node]])\n  end\n\n  defp filter_from_clauses(opts) do\n    cond do\n      Keyword.has_key?(opts, :user) -> opts |> Keyword.get(:user) |> Collection.by_user()\n      Keyword.has_key?(opts, :topic) -> opts |> Keyword.get(:topic) |> Collection.by_topic()\n      true -> Collection\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha/factory.ex",
    "content": "defmodule Mipha.Factory do\n  @moduledoc \"\"\"\n  ExMachina Factory funcs to use in tests.\n\n  ## Examples\n\n      iex> Factory.insert(:topic)\n      %Topic{}\n\n  \"\"\"\n\n  use ExMachina.Ecto, repo: Mipha.Repo\n\n  alias Mipha.Accounts.{User, Company, Team}\n  alias Mipha.Topics.{Topic, Node}\n  alias Mipha.Replies.Reply\n  alias Mipha.Collections.Collection\n  alias Mipha.Notifications.{Notification, UserNotification}\n  alias Mipha.Stars.Star\n  alias Mipha.Follows.Follow\n\n  @spec user_factory :: User.t()\n  def user_factory do\n    %User{\n      username: sequence(:username, &\"user#{&1}\"),\n      email: sequence(:email, &\"user#{&1}@elixir-mipha.com\"),\n      is_admin: false\n    }\n  end\n\n  @spec topic_factory :: Topic.t()\n  def topic_factory do\n    %Topic{\n      user: build(:user),\n      node: build(:node),\n      title: sequence(:title, &\"topic-title#{&1}\"),\n      body: sequence(:body, &\"topic-body#{&1}\")\n    }\n  end\n\n  @spec node_factory :: Node.t()\n  def node_factory do\n    %Node{\n      name: sequence(:name, &\"node-name#{&1}\")\n    }\n  end\n\n  @spec reply_factory :: Reply.t()\n  def reply_factory do\n    %Reply{\n      content: sequence(:content, &\"reply-content#{&1}\"),\n      user: build(:user),\n      topic: build(:topic)\n    }\n  end\n\n  @spec collection_factory :: Collection.t()\n  def collection_factory do\n    %Collection{\n      topic: build(:topic),\n      user: build(:user)\n    }\n  end\n\n  @spec notification_factory :: Notification.t()\n  def notification_factory do\n    %Notification{\n      actor: build(:user),\n      action: :followed\n    }\n  end\n\n  @spec user_notification_factory :: UserNotification.t()\n  def user_notification_factory do\n    %UserNotification{\n      user: build(:user),\n      notification: build(:notification)\n    }\n  end\n\n  @spec star_factory :: Star.t()\n  def star_factory do\n    %Star{\n      user: build(:user)\n    }\n  end\n\n  @spec follow_factory :: Follow.t()\n  def follow_factory do\n    %Follow{\n      follower: build(:user),\n      user: build(:user)\n    }\n  end\n\n  @spec company_factory :: Company.t()\n  def company_factory do\n    %Company{\n      name: sequence(:name, &\"company-name#{&1}\")\n    }\n  end\n\n  @spec team_factory :: Team.t()\n  def team_factory do\n    %Team{\n      name: sequence(:name, &\"team-name#{&1}\"),\n      owner: build(:user)\n    }\n  end\nend\n"
  },
  {
    "path": "lib/mipha/follows/follow.ex",
    "content": "defmodule Mipha.Follows.Follow do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.{Changeset, Query}\n\n  alias Mipha.{\n    Repo,\n    Accounts.User,\n    Follows.Follow\n  }\n\n  @type t :: %Follow{}\n\n  schema \"follows\" do\n    belongs_to :user, User\n    belongs_to :follower, User, foreign_key: :follower_id\n\n    timestamps()\n  end\n\n  @doc \"\"\"\n  Filters the follows by follower.\n  \"\"\"\n  @spec by_follower(Ecto.Queryable.t(), User.t()) :: Ecto.Query.t()\n  def by_follower(query \\\\ __MODULE__, %User{id: follower_id}),\n    do: from(f in query, where: f.follower_id == ^follower_id)\n\n  @doc \"\"\"\n  Filters the follows by followed user.\n  \"\"\"\n  @spec by_user(Ecto.Queryable.t(), User.t()) :: Ecto.Query.t()\n  def by_user(query \\\\ __MODULE__, %User{id: user_id}),\n    do: from(f in query, where: f.user_id == ^user_id)\n\n  @doc \"\"\"\n  Preloads the user of a topic.\n  \"\"\"\n  @spec preload_user(t()) :: t()\n  def preload_user(follow), do: Repo.preload(follow, [:user])\n\n  @doc false\n  def changeset(follow, attrs) do\n    permitted_attrs = ~w(\n      user_id\n      follower_id\n    )a\n\n    required_attrs = ~w(\n      user_id\n      follower_id\n    )a\n\n    follow\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/follows/follows.ex",
    "content": "defmodule Mipha.Follows do\n  @moduledoc \"\"\"\n  The Follows context.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias Ecto.Multi\n\n  alias Mipha.{\n    Repo,\n    Follows,\n    Accounts,\n    Notifications\n  }\n\n  alias Follows.Follow\n  alias Accounts.User\n\n  @doc \"\"\"\n  Returns the list of follows.\n\n  ## Examples\n\n      iex> list_follows()\n      [%Follow{}, ...]\n\n  \"\"\"\n  def list_follows do\n    Repo.all(Follow)\n  end\n\n  @doc \"\"\"\n  Gets a single follow.\n\n  Raises `Ecto.NoResultsError` if the Follow does not exist.\n\n  ## Examples\n\n      iex> get_follow!(123)\n      %Follow{}\n\n      iex> get_follow!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_follow!(id), do: Repo.get!(Follow, id)\n\n  @doc \"\"\"\n  Gets a single follow.\n\n  ## Examples\n\n      iex> get_follow(123)\n      %Follow{}\n\n      iex> get_follow(456)\n      nil\n\n      iex> get_follow(follower_id: 123, user_id: 456)\n      %Follow{}\n\n      iex> get_follow(follower_id: 123, user_id: 458)\n      nil\n\n  \"\"\"\n  @spec get_follow(String.t() | non_neg_integer) :: Follow.t() | nil\n  def get_follow(id) when not is_list(id), do: Repo.get(Follow, id)\n\n  @spec get_follow(Keyword.t()) :: Follow.t() | nil\n  def get_follow(clauses) when length(clauses) == 2, do: Repo.get_by(Follow, clauses)\n\n  @doc \"\"\"\n  Creates a follow.\n\n  ## Examples\n\n      iex> create_follow(%{field: value})\n      {:ok, %Follow{}}\n\n      iex> create_follow(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_follow(attrs \\\\ %{}) do\n    %Follow{}\n    |> Follow.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a follow.\n\n  ## Examples\n\n      iex> update_follow(follow, %{field: new_value})\n      {:ok, %Follow{}}\n\n      iex> update_follow(follow, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_follow(%Follow{} = follow, attrs) do\n    follow\n    |> Follow.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Follow.\n\n  ## Examples\n\n      iex> delete_follow(follow)\n      {:ok, %Follow{}}\n\n      iex> delete_follow(follow)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  @spec delete_follow(Follow.t()) :: {:ok, Follow.t()} | {:error, Ecto.Changeset.t()}\n  def delete_follow(%Follow{} = follow) do\n    Repo.delete(follow)\n  end\n\n  @spec delete_follow(Keyword.t()) :: {:ok, Follow.t()} | {:error, Ecto.Changeset.t()}\n  def delete_follow(clauses) when length(clauses) == 2 do\n    clauses\n    |> get_follow\n    |> Repo.delete()\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking follow changes.\n\n  ## Examples\n\n      iex> change_follow(follow)\n      %Ecto.Changeset{source: %Follow{}}\n\n  \"\"\"\n  def change_follow(%Follow{} = follow) do\n    Follow.changeset(follow, %{})\n  end\n\n  @doc \"\"\"\n  Unfollow user.\n\n  ## Examples\n\n      follow_user(follower: %User{}, user: %User{})\n      {:ok, follow}\n\n      follow_user(follower: %User{}, user: %User{})\n      {:error, _}\n\n  \"\"\"\n  @spec unfollow_user(Keyword.t()) :: {:ok, Follow.t()} | {:error, any()}\n  def unfollow_user(follower: follower, user: user) do\n    opts = [follower: follower, user: user]\n\n    if can_follow?(opts) do\n      if has_followed?(opts) do\n        delete_follow(user_id: user.id, follower_id: follower.id)\n      else\n        {:error, \"Unfollow already.\"}\n      end\n    else\n      {:error, \"Can not follower yourself.\"}\n    end\n  end\n\n  @doc \"\"\"\n  Follow user.\n  ## Examples\n\n      follow_user(follower: %User{}, user: %User{})\n      {:ok, follow}\n\n      follow_user(follower: %User{}, user: %User{})\n      {:error, _}\n\n  \"\"\"\n  @spec follow_user(Keyword.t()) :: {:ok, Follow.t()} | {:error, any()}\n  def follow_user(follower: follower, user: user) do\n    opts = [follower: follower, user: user]\n\n    if can_follow?(opts) do\n      if has_followed?(opts) do\n        {:error, \"Follow already.\"}\n      else\n        insert_follow(%{user_id: user.id, follower_id: follower.id})\n      end\n    else\n      {:error, \"Can't follower yourself.\"}\n    end\n  end\n\n  @doc \"\"\"\n  Returns `true` if the follower can follow the followable.\n  `false` otherwise.\n  \"\"\"\n  @spec can_follow?(Keyword.t()) :: boolean\n  def can_follow?(clauses) do\n    %User{id: follower_id} = Keyword.get(clauses, :follower)\n    %User{id: user_id} = Keyword.get(clauses, :user)\n\n    (user_id == follower_id && false) || true\n  end\n\n  @doc \"\"\"\n  Returns `true` if the user has followed the followable.\n  `false` otherwise.\n\n  ## Examples\n\n      iex> has_followed?(follower: %User{}, user: %User{})\n      true\n\n      iex> has_followed?(follower: %User{}, topic: %Topic{})\n      false\n\n  \"\"\"\n  @spec has_followed?(Keyword.t()) :: boolean\n  def has_followed?(clauses) do\n    # FIXME 这里添加 can_follow? 的逻辑\n    %User{id: follower_id} = Keyword.get(clauses, :follower)\n    %User{id: user_id} = Keyword.get(clauses, :user)\n\n    opts = [follower_id: follower_id, user_id: user_id]\n    !!get_follow(opts)\n  end\n\n  @doc \"\"\"\n  Insert a follow.\n\n  ## Examples\n\n      iex> insert_follow(attrs)\n      {:ok, %Follow{}}\n\n      iex> insert_follow(attrs)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def insert_follow(attrs \\\\ %{}) do\n    follow_changeset = Follow.changeset(%Follow{}, attrs)\n\n    Multi.new()\n    |> Multi.insert(:follow, follow_changeset)\n    |> notify_followee_of_follow()\n    |> Repo.transaction()\n  end\n\n  defp notify_followee_of_follow(multi) do\n    insert_notification_fn = fn _repo, %{follow: follow} ->\n      followee =\n        follow\n        |> Follow.preload_user()\n        |> Map.fetch!(:user)\n\n      notified_users = [followee]\n\n      notification_attrs = %{\n        user_id: followee.id,\n        actor_id: follow.follower_id,\n        action: \"followed\",\n        notified_users: notified_users\n      }\n\n      case Notifications.insert_notification(notification_attrs) do\n        {:ok, %{notification: notification}} -> {:ok, notification}\n        {:error, _, reason, _} -> {:error, reason}\n      end\n    end\n\n    Multi.run(multi, :notification_of_follow, insert_notification_fn)\n  end\n\n  @doc \"\"\"\n  Gets the follower count of a followable.\n\n  ## Examples\n\n      iex> get_follower_count([user: %User{}])\n      4\n\n      iex> get_follower_count([artwork: %Artwork{}])\n      8\n\n      iex> get_follower_count([topic: %Topic{}])\n      12\n\n  \"\"\"\n  @spec get_follower_count(Keyword.t()) :: non_neg_integer()\n  def get_follower_count(clauses) do\n    query =\n      clauses\n      |> Keyword.get(:user)\n      |> Follow.by_user()\n\n    query\n    |> join(:inner, [c], u in assoc(c, :follower))\n    |> Repo.aggregate(:count, :id)\n  end\n\n  @doc \"\"\"\n  Gets the followee count of a user.\n\n  ## Examples\n\n    iex> get_followee_count(%User{})\n    42\n\n  \"\"\"\n  @spec get_followee_count(User.t()) :: non_neg_integer()\n  def get_followee_count(%User{} = user) do\n    user\n    |> Follow.by_follower()\n    |> Repo.aggregate(:count, :id)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/follows/queries.ex",
    "content": "defmodule Mipha.Follows.Queries do\n  @moduledoc \"\"\"\n  Query Follows Contetxt.\n  \"\"\"\n  import Ecto.Query\n  alias Mipha.Follows.Follow\n\n  @doc \"\"\"\n  Filter the list of topics.\n\n  ## Examples\n\n      iex> cond_follows(params)\n      Ecto.Query.t()\n\n      iex> cond_follows(params, user: %User{})\n      Ecto.Query.t()\n\n      iex> cond_follows(params, follower: %User{})\n      Ecto.Query.t()\n\n  \"\"\"\n  @spec cond_follows(Keyword.t()) :: Ecto.Query.t()\n  def cond_follows(opts \\\\ []) do\n    opts\n    |> filter_from_clauses\n    |> preload([:user, :follower])\n  end\n\n  defp filter_from_clauses(opts) do\n    cond do\n      Keyword.has_key?(opts, :follower) -> opts |> Keyword.get(:follower) |> Follow.by_follower()\n      Keyword.has_key?(opts, :user) -> opts |> Keyword.get(:user) |> Follow.by_user()\n      true -> Follow\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha/mailer.ex",
    "content": "defmodule Mipha.Mailer do\n  @moduledoc false\n\n  use Bamboo.Mailer, otp_app: :mipha\nend\n"
  },
  {
    "path": "lib/mipha/markdown/auto_linker.ex",
    "content": "defmodule Mipha.Markdown.AutoLinker do\n  @moduledoc \"\"\"\n  AutoLinker will run on a string and return the string with any naked links\n  wrapped in an `a` tag with the link as both content and href..\n  \"\"\"\n\n  # A RegEx to match any URL that has spaces or newlines on either side of it.\n  @url_regex ~r{([ \\n]+|^)(http|ftp|https)://([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?}\n\n  def run(body) do\n    @url_regex\n    |> Regex.replace(body, \"\\\\1<a href=\\\"\\\\2\\://\\\\3\\\\4\\\\5\\\">\\\\2://\\\\3\\\\4\\\\5</a>\")\n  end\nend\n"
  },
  {
    "path": "lib/mipha/markdown/embed_video_replacer.ex",
    "content": "defmodule Mipha.Markdown.EmbedVideoReplacer do\n  @moduledoc false\n\n  @youtube_url_regex ~r{(\\s|^|<div>|<br>)(https?://)(www.)?(youtube\\.com/watch\\?v=|youtu\\.be/|youtube\\.com/watch\\?feature=player_embedded&v=)([A-Za-z0-9_\\-]*)(\\&\\S+)?(\\?\\S+)?}\n  @youku_url_regex ~r{(\\s|^|<div>|<br>)(http?://)(v\\.youku\\.com/v_show/id_)([a-zA-Z0-9\\-_\\=]*)(\\.html)(\\&\\S+)?(\\?\\S+)?}\n  @youku_base_url \"//player.youku.com/embed/\"\n  @youtube_base_url \"//www.youtube.com/embed/\"\n\n  def run(body) do\n    cond do\n      Regex.match?(@youtube_url_regex, body) == true ->\n        @youtube_url_regex\n        |> Regex.replace(body, &youtube_replacer/1)\n\n      Regex.match?(@youku_url_regex, body) == true ->\n        @youku_url_regex\n        |> Regex.replace(body, &youku_replacer/1)\n\n      true ->\n        body\n    end\n  end\n\n  defp youku_replacer(whole_match) do\n    youku_id =\n      @youku_url_regex\n      |> Regex.run(whole_match)\n      |> Enum.at(4)\n\n    embed_tag(\"#{@youku_base_url}#{youku_id}\")\n  end\n\n  defp youtube_replacer(whole_match) do\n    youtube_id =\n      @youtube_url_regex\n      |> Regex.run(whole_match)\n      |> Enum.at(5)\n\n    embed_tag(\"#{@youtube_base_url}#{youtube_id}\")\n  end\n\n  defp embed_tag(src) do\n    ~s(<span class=\"embed-responsive embed-responsive-16by9\">\n    <iframe class=\"embed-responsive-item\" src=\"#{src}\" allowfullscreen></iframe>\n    </span>)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/markdown/emoji_replacer.ex",
    "content": "defmodule Mipha.Markdown.EmojiReplacer do\n  @moduledoc \"\"\"\n  EmojiReplacer will run on a string and return the string with any emoji marks\n  (i.e. :poop:) replaced with their emoji counterpart.\n  \"\"\"\n\n  # A RegEx to match any emoji mark\n  @emoji_regex ~r{:([a-z]+):}\n\n  def run(body) do\n    @emoji_regex\n    |> Regex.replace(body, &emojify_short_name/2)\n  end\n\n  defp emojify_short_name(whole_match, short_name) do\n    case Exmoji.from_short_name(short_name) do\n      nil -> whole_match\n      emoji -> Exmoji.EmojiChar.render(emoji)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha/markdown/html_renderer.ex",
    "content": "# This is a clone of\n# https://raw.githubusercontent.com/pragdave/earmark/ea6382092c931ab4dd6d0dac6425430c78a61a6d/lib/earmark/html_renderer.ex\n# Justification for forking this module:\n# - Earmark explicitly provides support for this, but we can't reuse the\n#   existing render_* functions as they are private.\n# - No existing ability to customize the classes that are produced for the `pre`\n#   tag, which is necessary to use http://prismjs.com/plugins/line-numbers/\n\ndefmodule Mipha.Markdown.HtmlRenderer do\n  @moduledoc false\n\n  alias Earmark.Block\n  alias Earmark.Context\n  alias Earmark.Options\n  import Earmark.Inline, only: [convert: 3]\n  import Earmark.Helpers, only: [escape: 2]\n  import Earmark.Helpers.HtmlHelpers\n  import Earmark.Message, only: [add_messages_from: 2, add_messages: 2, get_messages: 1]\n  import Earmark.Context, only: [append: 2, set_value: 2]\n\n  alias Mipha.Markdown.{EmojiReplacer, MentionReplacer, AutoLinker, EmbedVideoReplacer}\n\n  def render(blocks, %Context{options: %Options{mapper: mapper}} = context) do\n    messages = get_messages(context)\n\n    {contexts, html} =\n      blocks\n      |> mapper.(&render_block(&1, put_in(context.options.messages, [])))\n      |> Enum.unzip()\n\n    all_messages =\n      contexts\n      |> Enum.reduce(messages, fn ctx, messages1 -> messages1 ++ get_messages(ctx) end)\n\n    {put_in(context.options.messages, all_messages), html |> IO.iodata_to_binary()}\n  end\n\n  # Paragraph\n  defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs}, context) do\n    lines =\n      lines\n      |> convert(lnb, context)\n      |> transform_value(&EmojiReplacer.run/1)\n      |> transform_value(&EmbedVideoReplacer.run/1)\n      |> transform_value(&MentionReplacer.run/1)\n      |> transform_value(&AutoLinker.run/1)\n\n    add_attrs!(lines, \"<p>#{lines.value}</p>\\n\", attrs, [], lnb)\n  end\n\n  # Html\n  defp render_block(%Block.Html{html: html}, context) do\n    {context, Enum.intersperse(html, ?\\n)}\n  end\n\n  defp render_block(%Block.HtmlOther{html: html}, context) do\n    {context, Enum.intersperse(html, ?\\n)}\n  end\n\n  # Ruler\n  defp render_block(%Block.Ruler{lnb: lnb, type: \"-\", attrs: attrs}, context) do\n    add_attrs!(context, \"<hr/>\\n\", attrs, [{\"class\", [\"thin\"]}], lnb)\n  end\n\n  defp render_block(%Block.Ruler{lnb: lnb, type: \"_\", attrs: attrs}, context) do\n    add_attrs!(context, \"<hr/>\\n\", attrs, [{\"class\", [\"medium\"]}], lnb)\n  end\n\n  defp render_block(%Block.Ruler{lnb: lnb, type: \"*\", attrs: attrs}, context) do\n    add_attrs!(context, \"<hr/>\\n\", attrs, [{\"class\", [\"thick\"]}], lnb)\n  end\n\n  # Heading\n  defp render_block(\n         %Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},\n         context\n       ) do\n    converted = convert(content, lnb, context)\n    html = \"<h#{level}>#{converted.value}</h#{level}>\\n\"\n    add_attrs!(converted, html, attrs, [], lnb)\n  end\n\n  # Blockquote\n  defp render_block(%Block.BlockQuote{lnb: lnb, blocks: blocks, attrs: attrs}, context) do\n    {context1, body} = render(blocks, context)\n    html = \"<blockquote>#{body}</blockquote>\\n\"\n    add_attrs!(context1, html, attrs, [], lnb)\n  end\n\n  # Table\n  defp render_block(\n         %Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},\n         context\n       ) do\n    cols = for _align <- aligns, do: \"<col>\\n\"\n    {context1, html} = add_attrs!(context, \"<table>\\n\", attrs, [], lnb)\n    html = [html, \"<colgroup>\\n\", cols, \"</colgroup>\\n\"]\n    context2 = set_value(context1, html)\n\n    context3 =\n      if header do\n        append(add_trs(append(context2, \"<thead>\\n\"), [header], \"th\", aligns, lnb), \"</thead>\\n\")\n      else\n        # Maybe an error, needed append(context, html)\n        context2\n      end\n\n    context4 = add_trs(context3, rows, \"td\", aligns, lnb)\n\n    {context4, [context4.value, \"</table>\\n\"]}\n  end\n\n  # Code\n  defp render_block(\n         %Block.Code{lnb: lnb, language: language, attrs: attrs} = block,\n         %Context{options: options} = context\n       ) do\n    class =\n      if language, do: ~s{ class=\"#{code_classes(language, options.code_class_prefix)}\"}, else: \"\"\n\n    tag = ~s[<pre><code#{class}>]\n    lines = options.render_code.(block)\n    html = ~s[#{tag}#{lines}</code></pre>\\n]\n    add_attrs!(context, html, attrs, [], lnb)\n  end\n\n  # Lists\n  defp render_block(\n         %Block.List{lnb: lnb, type: type, blocks: items, attrs: attrs, start: start},\n         context\n       ) do\n    {context1, content} = render(items, context)\n    html = \"<#{type}#{start}>\\n#{content}</#{type}>\\n\"\n    add_attrs!(context1, html, attrs, [], lnb)\n  end\n\n  # format a single paragraph list item, and remove the para tags\n  defp render_block(\n         %Block.ListItem{lnb: lnb, blocks: blocks, spaced: false, attrs: attrs},\n         context\n       )\n       when length(blocks) == 1 do\n    {context1, content} = render(blocks, context)\n    content = Regex.replace(~r{</?p>}, content, \"\")\n    html = \"<li>#{content}</li>\\n\"\n    add_attrs!(context1, html, attrs, [], lnb)\n  end\n\n  # format a spaced list item\n  defp render_block(%Block.ListItem{lnb: lnb, blocks: blocks, attrs: attrs}, context) do\n    {context1, content} = render(blocks, context)\n    html = \"<li>#{content}</li>\\n\"\n    add_attrs!(context1, html, attrs, [], lnb)\n  end\n\n  # Footnote Block\n  defp render_block(%Block.FnList{blocks: footnotes}, context) do\n    items =\n      Enum.map(footnotes, fn note ->\n        blocks = append_footnote_link(note)\n        %Block.ListItem{attrs: \"#fn:#{note.number}\", type: :ol, blocks: blocks}\n      end)\n\n    {context1, html} = render_block(%Block.List{type: :ol, blocks: items}, context)\n    {context1, Enum.join([~s[<div class=\"footnotes\">], \"<hr>\", html, \"</div>\"], \"\\n\")}\n  end\n\n  defp render_block(%Block.Ial{verbatim: verbatim}, context) do\n    {context, \"<p>{:#{verbatim}}</p>\\n\"}\n  end\n\n  # IDDef is ignored #\n  defp render_block(%Block.IdDef{}, context), do: {context, \"\"}\n\n  # Plugins\n  defp render_block(%Block.Plugin{lines: lines, handler: handler}, context) do\n    case handler.as_html(lines) do\n      html when is_list(html) -> {context, html}\n      {html, errors} -> {add_messages(context, errors), html}\n      html -> {context, [html]}\n    end\n  end\n\n  # And here are the inline renderers #\n  def br, do: \"<br/>\"\n  def codespan(text), do: ~s[<code class=\"inline\">#{text}</code>]\n  def em(text), do: \"<em>#{text}</em>\"\n  def strong(text), do: \"<strong>#{text}</strong>\"\n  def strikethrough(text), do: \"<del>#{text}</del>\"\n\n  def link(url, text), do: ~s[<a href=\"#{url}\">#{text}</a>]\n  def link(url, text, nil), do: ~s[<a href=\"#{url}\">#{text}</a>]\n  def link(url, text, title), do: ~s[<a href=\"#{url}\" title=\"#{title}\">#{text}</a>]\n\n  def image(path, alt, nil) do\n    ~s[<img src=\"#{path}\" alt=\"#{alt}\"/>]\n  end\n\n  def image(path, alt, title) do\n    ~s[<img src=\"#{path}\" alt=\"#{alt}\" title=\"#{title}\"/>]\n  end\n\n  def footnote_link(ref, backref, number),\n    do: ~s[<a href=\"##{ref}\" id=\"#{backref}\" class=\"footnote\" title=\"see footnote\">#{number}</a>]\n\n  # Table rows\n  def add_trs(context, rows, tag, aligns, lnb) do\n    numbered_rows =\n      rows\n      |> Enum.zip(Stream.iterate(lnb, &(&1 + 1)))\n\n    # for {row, lnb1} <- numbered_rows, do: \"<tr>\\n#{add_tds(context, row, tag, aligns, lnb1)}\\n</tr>\\n\"\n    numbered_rows\n    |> Enum.reduce(context, fn {row, lnb}, ctx ->\n      append(add_tds(append(ctx, \"<tr>\\n\"), row, tag, aligns, lnb), \"\\n</tr>\\n\")\n    end)\n  end\n\n  defp add_tds(context, row, tag, aligns, lnb) do\n    Enum.reduce(1..length(row), context, add_td_fn(row, tag, aligns, lnb))\n  end\n\n  defp add_td_fn(row, tag, aligns, lnb) do\n    fn n, ctx ->\n      style =\n        case Enum.at(aligns, n - 1, :default) do\n          :default -> \"\"\n          align -> \" style=\\\"text-align: #{align}\\\"\"\n        end\n\n      col = Enum.at(row, n - 1)\n      converted = convert(col, lnb, ctx)\n      append(add_messages_from(ctx, converted), \"<#{tag}#{style}>#{converted.value}</#{tag}>\")\n    end\n  end\n\n  # Append Footnote Return Link\n  def append_footnote_link(%Block.FnDef{} = note) do\n    fnlink =\n      ~s[<a href=\"#fnref:#{note.number}\" title=\"return to article\" class=\"reversefootnote\">&#x21A9;</a>]\n\n    [last_block | blocks] = Enum.reverse(note.blocks)\n    last_block = append_footnote_link(last_block, fnlink)\n\n    [last_block | blocks]\n    |> Enum.reverse()\n    |> List.flatten()\n  end\n\n  def append_footnote_link(%Block.Para{lines: lines} = block, fnlink) do\n    [last_line | lines] = Enum.reverse(lines)\n    last_line = \"#{last_line}&nbsp;#{fnlink}\"\n    [put_in(block.lines, Enum.reverse([last_line | lines]))]\n  end\n\n  def append_footnote_link(block, fnlink) do\n    [block, %Block.Para{lines: fnlink}]\n  end\n\n  def render_code(%Block.Code{lines: lines}) do\n    lines |> Enum.join(\"\\n\") |> escape(true)\n  end\n\n  defp code_classes(language, prefix) do\n    [\"\" | String.split(prefix || \"\")]\n    |> Enum.map(fn pfx -> \"#{pfx}#{language}\" end)\n    |> Enum.join(\" \")\n  end\n\n  defp transform_value(context, transformer) do\n    %{context | value: transformer.(context.value)}\n  end\nend\n"
  },
  {
    "path": "lib/mipha/markdown/markdown_guides.md",
    "content": "# Guide\n\n这是一篇讲解如何正确使用 **Markdown** 的排版示例，学会这个很有必要，能让你的文章有更佳清晰的排版。\n\n> 引用文本：Markdown is a text formatting syntax inspired\n\n## 语法指导\n\n### 普通内容\n\n这段内容展示了在内容里面一些小的格式，比如：\n\n- **加粗** - `**加粗**`\n- *倾斜* - `*倾斜*`\n- ~~删除线~~ - `~~删除线~~`\n- `Code 标记` - `\\`Code 标记\\``\n- [超级链接](http://github.com) - `[超级链接](http://github.com)`\n- [username@gmail.com](mailto:username@gmail.com) - `[username@gmail.com](mailto:username@gmail.com)`\n\n### 提及用户\n\n@foo @bar @someone @zven ... 通过 @ 可以在发帖和回帖里面提及用户，信息提交以后，被提及的用户将会收到系统通知。以便让他来关注这个帖子或回帖。\n\n### 表情符号 Emoji\n\n支持表情符号，你可以用系统默认的 Emoji 符号（无法支持 Windows 用户）。\n也可以用图片的表情，输入 `:` 将会出现智能提示。\n\n#### 一些表情例子\n\n:smile: :laughing: :dizzy_face: :sob: :cold_sweat: :sweat_smile:  :cry: :triumph: :heart_eyes: :relaxed: :sunglasses: :weary:\n\n:+1: :-1: :100: :clap: :bell: :gift: :question: :bomb: :heart: :coffee: :cyclone: :bow: :kiss: :pray: :sweat_drops: :hankey: :exclamation: :anger:\n\n### 大标题 - Heading 3\n\n你可以选择使用 H2 至 H6，使用 ##(N) 打头，H1 不能使用，会自动转换成 H2。\n\n> NOTE: 别忘了 # 后面需要有空格！\n\n#### Heading 4\n\n##### Heading 5\n\n###### Heading 6\n\n### 图片\n\n```\n![alt 文本](http://image-path.png)\n![alt 文本](http://image-path.png \"图片 Title 值\")\n![设置图片宽度高度](http://image-path.png =300x200)\n![设置图片宽度](http://image-path.png =300x)\n![设置图片高度](http://image-path.png =x200)\n```\n\n### 代码块\n\n#### 普通\n\n```\n*emphasize*    **strong**\n_emphasize_    __strong__\n@a = 1\n```\n\n#### 语法高亮支持\n\n如果在 \\`\\`\\` 后面更随语言名称，可以有语法高亮的效果哦，比如:\n\n##### 演示 Ruby 代码高亮\n\n```ruby\nclass PostController < ApplicationController\n  def index\n    @posts = Post.last_actived.limit(10)\n  end\nend\n```\n\n##### 演示 Rails View 高亮\n\n```erb\n<%= @posts.each do |post| %>\n<div class=\"post\"></div>\n<% end %>\n```\n\n##### 演示 YAML 文件\n\n```yml\nzh-CN:\n  name: 姓名\n  age: 年龄\n```\n\n> Tip: 语言名称支持下面这些: `ruby`, `python`, `js`, `html`, `erb`, `css`, `coffee`, `bash`, `json`, `yml`, `xml` ...\n\n### 有序、无序列表\n\n#### 无序列表\n\n- Ruby\n  - Rails\n    - ActiveRecord\n- Go\n  - Gofmt\n  - Revel\n- Node.js\n  - Koa\n  - Express\n\n#### 有序列表\n\n1. Node.js\n  1. Express\n  2. Koa\n  3. Sails\n2. Ruby\n  1. Rails\n  2. Sinatra\n3. Go\n\n### 表格\n\n如果需要展示数据什么的，可以选择使用表格哦\n\n| header 1 | header 3 |\n| -------- | -------- |\n| cell 1   | cell 2   |\n| cell 3   | cell 4   |\n| cell 5   | cell 6   |\n\n### 段落\n\n留空白的换行，将会被自动转换成一个段落，会有一定的段落间距，便于阅读。\n\n请注意后面 Markdown 源代码的换行留空情况。\n\n### 视频插入\n\n目前支持 Youtube 和 Youku 两家的视频插入，你只需要复制视频播放页面，浏览器地址栏的网页 URL 地址，并粘贴到话题／回复文本框，提交以后将自动转换成视频播放器。\n\n#### 例如\n\n**Youtube**\n\nhttps://www.youtube.com/watch?v=CvVvwh3BRq8\n\n**Vimeo**\n\nhttps://vimeo.com/199770305\n\n**Youku**\n\nhttp://v.youku.com/v_show/id_XMjQzMTY1MDk3Ng==.html\n\n···"
  },
  {
    "path": "lib/mipha/markdown/mention_replacer.ex",
    "content": "defmodule Mipha.Markdown.MentionReplacer do\n  @moduledoc \"\"\"\n  Replacer\n  \"\"\"\n\n  alias Mipha.Accounts\n\n  @user_regex ~r{@([a-z1-9]+)}\n\n  def run(body) do\n    @user_regex\n    |> Regex.replace(body, &existed_user_replacer/2)\n  end\n\n  defp existed_user_replacer(whole_match, match_name) do\n    case Accounts.get_user_by_username(match_name) do\n      nil -> whole_match\n      user -> ~s(<a href=\"/u/#{user.username}\" title=\"#{user.username}\">#{user.username}</a>)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha/markdown.ex",
    "content": "defmodule Mipha.Markdown do\n  @moduledoc \"\"\"\n  Sanitize a string's HTML, then render the string as markdown in the Mipha style.\n  \"\"\"\n\n  def render(body) do\n    body\n    |> as_html!()\n  end\n\n  defp as_html!(body) do\n    body\n    |> HtmlSanitizeEx.markdown_html()\n    |> Earmark.as_html!(earmark_options())\n  end\n\n  def example do\n    File.read!(\"./lib/mipha/markdown/markdown_guides.md\")\n  end\n\n  def earmark_options do\n    %Earmark.Options{\n      # Prefix the `code` tag language class, as in `language-elixir`, for\n      # proper support from http://prismjs.com/\n      code_class_prefix: \"language-\",\n      renderer: Mipha.Markdown.HtmlRenderer,\n      gfm: true,\n      breaks: true,\n      smartypants: false\n    }\n  end\nend\n"
  },
  {
    "path": "lib/mipha/notifications/notification.ex",
    "content": "defmodule Mipha.Notifications.Notification do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.{Changeset, Query}\n  import EctoEnum, only: [defenum: 3]\n\n  alias Mipha.Repo\n  alias Mipha.Accounts.User\n  alias Mipha.Topics.Topic\n  alias Mipha.Replies.Reply\n  alias Mipha.Notifications.{Notification, UserNotification}\n\n  @type t :: %Notification{}\n\n  # Support as follows\n  # :topic_reply_added\n  # :topic_starred\n  # :reply_starred\n  # :reply_comment_added\n  # :topic_mentioned\n  # :reply_mentioned\n  # :followed\n  # Follow user action\n  #   :topic_added\n  #   :topic_reply_added\n  defenum(NotificationAction, :notification_action, [\n    :topic_reply_added,\n    :topic_starred,\n    :reply_starred,\n    :reply_comment_added,\n    :topic_mentioned,\n    :reply_mentioned,\n    :followed,\n    :topic_added\n  ])\n\n  schema \"notifications\" do\n    field :action, NotificationAction\n\n    belongs_to :actor, User, foreign_key: :actor_id\n    belongs_to :reply, Reply\n    belongs_to :topic, Topic\n    belongs_to :user, User\n\n    has_many :user_notifications, UserNotification\n\n    many_to_many :notified_users, User, join_through: UserNotification\n\n    timestamps()\n  end\n\n  @doc \"\"\"\n  Filters the notifications by actor.\n  \"\"\"\n  @spec by_actor(Ecto.Queryable.t(), User.t()) :: Ecto.Query.t()\n  def by_actor(query \\\\ Notification, %User{id: actor_id}),\n    do: where(query, [..., n], n.actor_id == ^actor_id)\n\n  @doc \"\"\"\n  Preloads the actor of a notification.\n  \"\"\"\n  @spec preload_actor(t()) :: t()\n  def preload_actor(%Notification{} = notification), do: Repo.preload(notification, :actor)\n\n  @doc \"\"\"\n  Preloads the topic of a notification.\n  \"\"\"\n  @spec preload_topic(t()) :: t()\n  def preload_topic(%Notification{} = notification), do: Repo.preload(notification, :topic)\n\n  @doc \"\"\"\n  Preloads the reply of a notification.\n  \"\"\"\n  @spec preload_reply(t()) :: t()\n  def preload_reply(%Notification{} = notification), do: Repo.preload(notification, :reply)\n\n  @doc \"\"\"\n  Preloads the user of a notification.\n  \"\"\"\n  @spec preload_user(t()) :: t()\n  def preload_user(%Notification{} = notification), do: Repo.preload(notification, :user)\n\n  @doc false\n  def changeset(notification, attrs) do\n    permitted_attrs = ~w(\n      action\n      actor_id\n      topic_id\n      reply_id\n      user_id\n    )a\n\n    required_attrs = ~w(\n      action\n    )a\n\n    notification\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n    |> assoc_constraint(:actor)\n    |> assoc_constraint(:topic)\n    |> assoc_constraint(:reply)\n    |> assoc_constraint(:user)\n    |> maybe_put_notified_users(attrs)\n  end\n\n  @spec maybe_put_notified_users(Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()\n  defp maybe_put_notified_users(%Ecto.Changeset{} = changeset, attrs) do\n    case Map.get(attrs, :notified_users) do\n      value when not is_nil(value) -> put_assoc(changeset, :notified_users, value)\n      nil -> changeset\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha/notifications/notifications.ex",
    "content": "defmodule Mipha.Notifications do\n  @moduledoc \"\"\"\n  The Notifications context.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias Ecto.Multi\n\n  alias Mipha.{\n    Repo,\n    Topics,\n    Replies,\n    Accounts,\n    Notifications\n  }\n\n  alias Notifications.{Notification, UserNotification}\n  alias Topics.Topic\n  alias Replies.Reply\n  alias Accounts.User\n\n  @type notification_object :: Topic.t() | Reply.t() | User.t()\n\n  @doc \"\"\"\n  Returns the list of notifications.\n\n  ## Examples\n\n      iex> list_notifications()\n      [%Notification{}, ...]\n\n  \"\"\"\n  def list_notifications do\n    Repo.all(Notification)\n  end\n\n  @doc \"\"\"\n  Gets a single notification.\n\n  Raises `Ecto.NoResultsError` if the Notification does not exist.\n\n  ## Examples\n\n      iex> get_notification!(123)\n      %Notification{}\n\n      iex> get_notification!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_notification!(id), do: Repo.get!(Notification, id)\n\n  @doc \"\"\"\n  Creates a notification.\n\n  ## Examples\n\n      iex> create_notification(%{field: value})\n      {:ok, %Notification{}}\n\n      iex> create_notification(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_notification(attrs \\\\ %{}) do\n    %Notification{}\n    |> Notification.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a notification.\n\n  ## Examples\n\n      iex> update_notification(notification, %{field: new_value})\n      {:ok, %Notification{}}\n\n      iex> update_notification(notification, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_notification(%Notification{} = notification, attrs) do\n    notification\n    |> Notification.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Notification.\n\n  ## Examples\n\n      iex> delete_notification(notification)\n      {:ok, %Notification{}}\n\n      iex> delete_notification(notification)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_notification(%Notification{} = notification) do\n    Repo.delete(notification)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking notification changes.\n\n  ## Examples\n\n      iex> change_notification(notification)\n      %Ecto.Changeset{source: %Notification{}}\n\n  \"\"\"\n  def change_notification(%Notification{} = notification) do\n    Notification.changeset(notification, %{})\n  end\n\n  @doc \"\"\"\n  标记单个已读\n  \"\"\"\n  @spec read_notification(UserNotification.t()) ::\n          {:ok, UserNotification.t()} | {:error, %Ecto.Changeset{}}\n  def read_notification(%UserNotification{} = user_notification) do\n    attrs = %{read_at: Timex.now()}\n\n    user_notification\n    |> UserNotification.update_changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  标注所有未读信息为已读\n  \"\"\"\n  @spec mark_read_notification(User.t()) :: any\n  def mark_read_notification(user) do\n    user\n    |> UserNotification.by_user()\n    |> UserNotification.unread()\n    |> Repo.update_all(set: [read_at: Timex.now()])\n  end\n\n  @doc \"\"\"\n  清空通知\n  \"\"\"\n  @spec clean_notification(User.t()) :: any\n  def clean_notification(user) do\n    user\n    |> UserNotification.by_user()\n    |> Repo.delete_all()\n  end\n\n  @doc \"\"\"\n  获取通知的发送者。\n\n  ## Examples\n\n      iex> actor(%Notification{})\n      %User{}\n\n      iex> actor(%Notification{})\n      nil\n\n  \"\"\"\n  @spec actor(Notification.t()) :: User.t() | nil\n  def actor(%Notification{} = notification) do\n    notification\n    |> Notification.preload_actor()\n    |> Map.get(:actor)\n  end\n\n  @doc \"\"\"\n  获取通知对象 topic || reply || user\n\n  ## Examples\n\n      iex> object(%Notification{})\n      %Topic{}\n\n      iex> object(%Notification{})\n      %Reply{}\n\n      iex> object(%Notification{})\n      %User{}\n\n  \"\"\"\n  @spec object(Notification.t()) :: notification_object\n  def object(%Notification{topic_id: topic_id} = notification) when not is_nil(topic_id) do\n    notification\n    |> Notification.preload_topic()\n    |> Map.get(:topic)\n  end\n\n  def object(%Notification{reply_id: reply_id} = notification) when not is_nil(reply_id) do\n    notification\n    |> Notification.preload_reply()\n    |> Map.get(:reply)\n    |> Reply.preload_topic()\n    |> Reply.preload_parent()\n  end\n\n  def object(%Notification{user_id: user_id} = notification) when not is_nil(user_id) do\n    notification\n    |> Notification.preload_user()\n    |> Map.get(:user)\n  end\n\n  def object(_), do: nil\n\n  @doc \"\"\"\n  获取未读的 Notification 个数\n  \"\"\"\n  @spec unread_notification_count(User.t()) :: non_neg_integer()\n  def unread_notification_count(user) do\n    user\n    |> UserNotification.by_user()\n    |> UserNotification.unread()\n    |> Repo.aggregate(:count, :id)\n  end\n\n  @doc \"\"\"\n  Returns the list of users_notifications.\n\n  ## Examples\n\n      iex> list_users_notifications()\n      [%UserNotification{}, ...]\n\n  \"\"\"\n  def list_users_notifications do\n    Repo.all(UserNotification)\n  end\n\n  @doc \"\"\"\n  Gets a single user_notification.\n\n  Raises `Ecto.NoResultsError` if the User notification does not exist.\n\n  ## Examples\n\n      iex> get_user_notification!(123)\n      %UserNotification{}\n\n      iex> get_user_notification!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_user_notification!(id), do: Repo.get!(UserNotification, id)\n\n  @doc \"\"\"\n  Creates a user_notification.\n\n  ## Examples\n\n      iex> create_user_notification(%{field: value})\n      {:ok, %UserNotification{}}\n\n      iex> create_user_notification(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_user_notification(attrs \\\\ %{}) do\n    %UserNotification{}\n    |> UserNotification.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Inserts a notification.\n  \"\"\"\n  @spec insert_notification(any) :: {:ok, any} | {:error, any, any, any}\n  def insert_notification(attrs) do\n    Multi.new()\n    |> insert_notification(attrs)\n    |> Repo.transaction()\n  end\n\n  @spec insert_notification(Multi.t(), map()) :: Multi.t()\n  defp insert_notification(multi, attrs) do\n    notification_changeset = Notification.changeset(%Notification{}, attrs)\n\n    Multi.insert(multi, :notification, notification_changeset)\n  end\n\n  @doc \"\"\"\n  Updates a user_notification.\n\n  ## Examples\n\n      iex> update_user_notification(user_notification, %{field: new_value})\n      {:ok, %UserNotification{}}\n\n      iex> update_user_notification(user_notification, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_user_notification(%UserNotification{} = user_notification, attrs) do\n    user_notification\n    |> UserNotification.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a UserNotification.\n\n  ## Examples\n\n      iex> delete_user_notification(user_notification)\n      {:ok, %UserNotification{}}\n\n      iex> delete_user_notification(user_notification)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_user_notification(%UserNotification{} = user_notification) do\n    Repo.delete(user_notification)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking user_notification changes.\n\n  ## Examples\n\n      iex> change_user_notification(user_notification)\n      %Ecto.Changeset{source: %UserNotification{}}\n\n  \"\"\"\n  def change_user_notification(%UserNotification{} = user_notification) do\n    UserNotification.changeset(user_notification, %{})\n  end\nend\n"
  },
  {
    "path": "lib/mipha/notifications/queries.ex",
    "content": "defmodule Mipha.Notifications.Queries do\n  @moduledoc false\n\n  import Ecto.Query\n\n  alias Mipha.Accounts.User\n  alias Mipha.Notifications.UserNotification\n\n  @doc \"\"\"\n  Returns current_user notification.\n  \"\"\"\n  @spec cond_user_notifications(User.t()) :: Ecto.Query.t()\n  def cond_user_notifications(%User{} = user) do\n    user\n    |> UserNotification.by_user()\n    |> preload([:user, :notification])\n  end\nend\n"
  },
  {
    "path": "lib/mipha/notifications/user_notification.ex",
    "content": "defmodule Mipha.Notifications.UserNotification do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.{Changeset, Query}\n\n  alias Mipha.Repo\n  alias Mipha.Accounts.User\n  alias Mipha.Notifications.{Notification, UserNotification}\n\n  @type t :: %UserNotification{}\n\n  schema \"users_notifications\" do\n    field :read_at, :naive_datetime\n\n    belongs_to :user, User\n    belongs_to :notification, Notification\n\n    timestamps()\n  end\n\n  @doc \"\"\"\n  Filters the user notifications by user.\n  \"\"\"\n  @spec by_user(Ecto.Queryable.t(), User.t()) :: Ecto.Query.t()\n  def by_user(query \\\\ UserNotification, %User{id: user_id}),\n    do: where(query, [..., un], un.user_id == ^user_id)\n\n  @doc \"\"\"\n  Gets unread user notifications\n  \"\"\"\n  @spec unread(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def unread(query \\\\ __MODULE__),\n    do: where(query, [..., un], is_nil(un.read_at))\n\n  @doc \"\"\"\n  Preloads the user of a user notification.\n  \"\"\"\n  @spec preload_user(t()) :: t()\n  def preload_user(%UserNotification{} = user_notification),\n    do: Repo.preload(user_notification, :user)\n\n  @doc \"\"\"\n  标记已读\n  \"\"\"\n  @spec update_changeset(t(), map()) :: Ecto.Changeset.t()\n  def update_changeset(%UserNotification{} = user_notification, attrs) do\n    permitted_attrs = ~w(\n      read_at\n    )a\n\n    user_notification\n    |> cast(attrs, permitted_attrs)\n  end\n\n  @doc \"\"\"\n  Changeset\n  \"\"\"\n  def changeset(user_notification, attrs) do\n    permitted_attrs = ~w(\n      user_id\n      notification_id\n      read_at\n    )a\n\n    required_attrs = ~w(\n      user_id\n      notification_id\n    )a\n\n    user_notification\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/qiniu.ex",
    "content": "defmodule Mipha.Qiniu do\n  @moduledoc false\n\n  @base_url \"http://pbfwruvmm.bkt.clouddn.com/\"\n  @bucket \"mipha\"\n\n  def upload(path) do\n    @bucket\n    |> Qiniu.PutPolicy.build()\n    |> Qiniu.Uploader.upload(path, key: generate_key())\n  end\n\n  @doc \"\"\"\n  获取七牛的链接地址\n  \"\"\"\n  def q_url(value) when is_nil(value) do\n    @base_url <> \"default.jpg\"\n  end\n\n  def q_url(value) do\n    @base_url <> value\n  end\n\n  defp generate_key do\n    \"#{Timex.to_unix(Timex.now())}/#{Enum.random(1..1_000)}.jpg\"\n  end\nend\n"
  },
  {
    "path": "lib/mipha/regexp.ex",
    "content": "defmodule Mipha.Regexp do\n  @moduledoc \"\"\"\n  Regex\n  \"\"\"\n\n  def username do\n    ~r/^[a-z\\d](?:[a-z\\d]|-(?=[a-z\\d])){1,64}$/\n  end\n\n  def email do\n    ~r/^[A-Za-z0-9._%+-+']+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,32}$/\n  end\nend\n"
  },
  {
    "path": "lib/mipha/replies/queries.ex",
    "content": "defmodule Mipha.Replies.Queries do\n  @moduledoc false\n\n  import Ecto.Query\n  alias Mipha.Replies.Reply\n\n  @doc \"\"\"\n  Returns the list replies.\n  \"\"\"\n  @spec list_replies :: Ecto.Query.t()\n  def list_replies, do: Reply\n\n  @doc \"\"\"\n  Filter the list of repies.\n\n  ## Examples\n\n      iex> cond_replies(params)\n      Ecto.Query.t()\n\n      iex> cond_replies(params, user: %User{})\n      Ecto.Query.t()\n\n  \"\"\"\n  @spec cond_replies(Keyword.t()) :: Ecto.Query.t()\n  def cond_replies(opts) do\n    opts\n    |> filter_from_clauses\n    |> preload([:topic, :user])\n  end\n\n  defp filter_from_clauses(opts) do\n    cond do\n      Keyword.has_key?(opts, :user) -> opts |> Keyword.get(:user) |> Reply.by_user()\n      Keyword.has_key?(opts, :topic) -> opts |> Keyword.get(:topic) |> Reply.by_topic()\n      true -> Reply\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha/replies/replies.ex",
    "content": "defmodule Mipha.Replies do\n  @moduledoc \"\"\"\n  The Replies context.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias Ecto.Multi\n\n  alias Mipha.{\n    Repo,\n    Topics,\n    Notifications,\n    Replies,\n    Accounts\n  }\n\n  alias Replies.Reply\n  alias Topics.Topic\n  alias Accounts.User\n  alias Mipha.Follows.Follow\n\n  @username_regex ~r{@([A-Za-z0-9]+)}\n\n  @doc \"\"\"\n  Returns the list of repies.\n\n  ## Examples\n\n      iex> list_repies()\n      [%Reply{}, ...]\n\n  \"\"\"\n  def list_repies do\n    Repo.all(Reply)\n  end\n\n  @doc \"\"\"\n  Gets a single reply.\n\n  Raises `Ecto.NoResultsError` if the Reply does not exist.\n\n  ## Examples\n\n      iex> get_reply!(123)\n      %Reply{}\n\n      iex> get_reply!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_reply!(id), do: Repo.get!(Reply, id)\n\n  @doc \"\"\"\n  Creates a reply.\n\n  ## Examples\n\n      iex> create_reply(%{field: value})\n      {:ok, %Reply{}}\n\n      iex> create_reply(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_reply(attrs \\\\ %{}) do\n    %Reply{}\n    |> Reply.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a reply.\n\n  ## Examples\n\n      iex> update_reply(reply, %{field: new_value})\n      {:ok, %Reply{}}\n\n      iex> update_reply(reply, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_reply(%Reply{} = reply, attrs) do\n    reply\n    |> Reply.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Reply.\n\n  ## Examples\n\n      iex> delete_reply(reply)\n      {:ok, %Reply{}}\n\n      iex> delete_reply(reply)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_reply(%Reply{} = reply) do\n    Multi.new()\n    |> Multi.delete(:reply, reply)\n    |> decrease_topic_reply_count()\n    |> Repo.transaction()\n  end\n\n  defp decrease_topic_reply_count(multi) do\n    update_topic_fn = fn _repo, %{reply: reply} ->\n      topic =\n        reply\n        |> Reply.preload_topic()\n        |> Map.fetch!(:topic)\n\n      attrs = %{reply_count: topic.reply_count - 1}\n      Topics.update_topic(topic, attrs)\n    end\n\n    Multi.run(multi, :decrease_topic_reply_count, update_topic_fn)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking reply changes.\n\n  ## Examples\n\n      iex> change_reply(reply)\n      %Ecto.Changeset{source: %Reply{}}\n\n  \"\"\"\n  def change_reply(%Reply{} = reply) do\n    Reply.changeset(reply, %{})\n  end\n\n  @doc \"\"\"\n  Get the reply count of a replyable.\n\n  ## Examples\n\n      iex> get_reply_count(user: %User{})\n      10\n\n      iex> get_reply_count(topic: %Topic{})\n      2\n\n  \"\"\"\n  @spec get_reply_count(Keyword.t()) :: non_neg_integer()\n  def get_reply_count(clauses) do\n    query =\n      clauses\n      |> get_replyable_from_clauses()\n      |> case do\n        %Topic{} = topic -> Reply.by_topic(topic)\n        %Reply{} = reply -> Reply.by_parent(reply)\n        %User{} = user -> Reply.by_user(user)\n      end\n\n    query\n    |> join(:inner, [r], u in assoc(r, :user))\n    |> Repo.aggregate(:count, :id)\n  end\n\n  @doc \"\"\"\n  Gets all reply count.\n  \"\"\"\n  @spec get_total_reply_count :: non_neg_integer()\n  def get_total_reply_count do\n    Reply\n    |> Repo.aggregate(:count, :id)\n  end\n\n  defp get_replyable_from_clauses(clauses) do\n    cond do\n      Keyword.has_key?(clauses, :user) -> Keyword.get(clauses, :user)\n      Keyword.has_key?(clauses, :topic) -> Keyword.get(clauses, :topic)\n      Keyword.has_key?(clauses, :reply) -> Keyword.get(clauses, :reply)\n    end\n  end\n\n  @doc \"\"\"\n  Return the recent of topics.\n  \"\"\"\n  @spec recent_replies(User.t()) :: [Topic.t()] | nil\n  def recent_replies(%User{} = user) do\n    user\n    |> Reply.by_user()\n    |> Reply.recent()\n    |> Repo.all()\n    |> Repo.preload([:topic, :user])\n  end\n\n  @doc \"\"\"\n  Inserts a reply.\n\n  ## Examples\n\n      iex> insert_reply(%User{}, %{field: value})\n      {:ok, %Reply{}}\n\n      iex> insert_reply(%User{}, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  @spec insert_reply(User.t(), map()) :: {:ok, Reply.t()} | {:error, Ecto.Changeset.t()}\n  def insert_reply(user, attrs \\\\ %{}) do\n    attrs = attrs |> Map.put(\"user_id\", user.id)\n    reply_changeset = Reply.changeset(%Reply{}, attrs)\n\n    Multi.new()\n    |> Multi.insert(:reply, reply_changeset)\n    |> update_related_topic()\n    |> maybe_notify_follower_of_new_reply()\n    |> notify_topic_owner_of_new_reply()\n    |> maybe_notify_mention_users_of_new_reply(attrs)\n    |> maybe_notify_parent_reply_owner_of_new_reply(attrs)\n    |> Repo.transaction()\n  end\n\n  # Notification mention @ users.\n  def maybe_notify_mention_users_of_new_reply(multi, attrs) do\n    insert_notification_fn = fn %{reply: reply} ->\n      notified_users =\n        @username_regex\n        |> Regex.scan(attrs[\"content\"])\n        |> Enum.map(fn [_, match] -> Accounts.get_user_by_username(match) end)\n        |> Enum.filter(&(not is_nil(&1)))\n\n      attrs = %{\n        actor_id: reply.user_id,\n        action: \"reply_mentioned\",\n        reply_id: reply.id,\n        notified_users: notified_users\n      }\n\n      case Notifications.insert_notification(attrs) do\n        {:ok, %{notification: notification}} -> {:ok, notification}\n        {:error, _, reason, _} -> {:error, reason}\n      end\n    end\n\n    Multi.run(multi, :notify_mention_users_of_new_reply, insert_notification_fn)\n  end\n\n  # Update assoc topic.\n  defp update_related_topic(multi) do\n    update_topic_fn = fn %{reply: reply} ->\n      topic =\n        reply\n        |> Reply.preload_topic()\n        |> Map.fetch!(:topic)\n\n      attrs = %{\n        last_reply_id: reply.id,\n        last_reply_user_id: reply.user_id,\n        reply_count: topic.reply_count + 1\n      }\n\n      case Topics.update_topic(topic, attrs) do\n        {:ok, topic} -> {:ok, topic}\n        {:error, reason} -> {:error, reason}\n      end\n    end\n\n    Multi.run(multi, :update_related_topic, update_topic_fn)\n  end\n\n  # Notificaton topic owner.\n  defp notify_topic_owner_of_new_reply(multi) do\n    insert_notification_fn = fn %{reply: reply} ->\n      # reply -> topic -> user\n      notified_users =\n        reply\n        |> Reply.preload_topic()\n        |> Map.fetch!(:topic)\n        |> Topic.preload_user()\n        |> Map.fetch!(:user)\n\n      attrs = %{\n        actor_id: reply.user_id,\n        action: \"topic_reply_added\",\n        reply_id: reply.id,\n        notified_users: [notified_users]\n      }\n\n      case Notifications.insert_notification(attrs) do\n        {:ok, %{notification: notification}} -> {:ok, notification}\n        {:error, _, reason, _} -> {:error, reason}\n      end\n    end\n\n    Multi.run(multi, :notify_topic_owner_of_new_reply, insert_notification_fn)\n  end\n\n  # 如果是回复其他人的评论，回复该评论的作者, 有新的回复。\n  defp maybe_notify_parent_reply_owner_of_new_reply(multi, %{\"parent_id\" => parent_id})\n       when parent_id != \"\" do\n    insert_notification_fn = fn %{reply: reply} ->\n      notified_users =\n        reply\n        |> Reply.preload_parent()\n        |> Map.fetch!(:parent)\n        |> Reply.preload_user()\n        |> Map.fetch!(:user)\n\n      attrs = %{\n        actor_id: reply.user_id,\n        action: \"reply_comment_added\",\n        reply_id: reply.id,\n        notified_users: [notified_users]\n      }\n\n      case Notifications.insert_notification(attrs) do\n        {:ok, %{notification: notification}} -> {:ok, notification}\n        {:error, _, reason, _} -> {:error, reason}\n      end\n    end\n\n    Multi.run(multi, :notify_parent_reply_owner_of_new_reply, insert_notification_fn)\n  end\n\n  defp maybe_notify_parent_reply_owner_of_new_reply(multi, _), do: multi\n\n  # 发起评论时，通知关注评论作者的 follower\n  defp maybe_notify_follower_of_new_reply(multi) do\n    insert_notification_fn = fn %{reply: reply} ->\n      # 取关注评论作者的 follower.\n      notified_users = notifiable_users_of_reply(reply)\n\n      attrs = %{\n        actor_id: reply.user_id,\n        action: \"topic_reply_added\",\n        reply_id: reply.id,\n        notified_users: notified_users\n      }\n\n      case Notifications.insert_notification(attrs) do\n        {:ok, %{notification: notification}} -> {:ok, notification}\n        {:error, _, reason, _} -> {:error, reason}\n      end\n    end\n\n    Multi.run(multi, :notify_users_of_new_reply, insert_notification_fn)\n  end\n\n  defp notifiable_users_of_reply(%Reply{} = reply) do\n    query =\n      from u in User,\n        join: f in Follow,\n        on: f.follower_id == u.id,\n        where: f.user_id == ^reply.user_id\n\n    Repo.all(query)\n  end\n\n  @doc \"\"\"\n  Gets reply author.\n\n  ## Example\n\n      iex> author(%Topic{})\n      %User{}\n\n  \"\"\"\n  @spec author(Reply.t()) :: User.t()\n  def author(%Reply{} = reply) do\n    reply\n    |> Reply.preload_user()\n    |> Map.fetch!(:user)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/replies/reply.ex",
    "content": "defmodule Mipha.Replies.Reply do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.{Changeset, Query}\n\n  alias Mipha.{\n    Repo,\n    Topics.Topic,\n    Replies.Reply,\n    Accounts.User,\n    Stars.Star\n  }\n\n  @type t :: %Reply{}\n\n  schema \"replies\" do\n    field :content, :string\n    field :star_count, :integer, default: 0\n\n    belongs_to :user, User\n    belongs_to :topic, Topic\n    belongs_to :parent, Reply, foreign_key: :parent_id\n\n    has_many :children, Reply, foreign_key: :parent_id\n    has_many :stars, Star\n\n    timestamps()\n  end\n\n  @doc \"\"\"\n  Returns the children node.\n  \"\"\"\n  @spec is_child(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def is_child(query \\\\ __MODULE__),\n    do: from(q in query, where: not is_nil(q.parent_id))\n\n  @doc \"\"\"\n  Filters the topic by reply.\n  \"\"\"\n  @spec by_topic(Ecto.Queryable.t(), Topic.t()) :: Ecto.Query.t()\n  def by_topic(query \\\\ __MODULE__, %Topic{id: topic_id}),\n    do: where(query, [..., r], r.topic_id == ^topic_id)\n\n  @doc \"\"\"\n  Filters the parent by reply.\n  \"\"\"\n  @spec by_parent(Ecto.Queryable.t(), t()) :: Ecto.Query.t()\n  def by_parent(query \\\\ __MODULE__, %__MODULE__{id: parent_id}),\n    do: where(query, [..., r], r.parent_id == ^parent_id)\n\n  @doc \"\"\"\n  Filters reply by user.\n  \"\"\"\n  @spec by_user(Ecto.Queryable.t(), User.t()) :: Ecto.Query.t()\n  def by_user(query \\\\ __MODULE__, %User{id: user_id}),\n    do: where(query, [..., r], r.user_id == ^user_id)\n\n  @doc \"\"\"\n  Get the recent of replies.\n  \"\"\"\n  @spec recent(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def recent(query \\\\ __MODULE__),\n    do: from(r in query, order_by: [desc: r.id], limit: 10)\n\n  @doc \"\"\"\n  Preloads the user of a reply.\n  \"\"\"\n  @spec preload_user(t()) :: t()\n  def preload_user(reply), do: Repo.preload(reply, [:user])\n\n  @doc \"\"\"\n  Preloads the topic of a reply.\n  \"\"\"\n  @spec preload_topic(t()) :: t()\n  def preload_topic(reply), do: Repo.preload(reply, [:topic])\n\n  @doc \"\"\"\n  Preloads the parent of a reply.\n  \"\"\"\n  @spec preload_parent(t()) :: t()\n  def preload_parent(reply), do: Repo.preload(reply, [:parent])\n\n  @doc false\n  def changeset(reply, attrs) do\n    permitted_attrs = ~w(\n      star_count\n      topic_id\n      user_id\n      parent_id\n      content\n    )a\n\n    required_attrs = ~w(\n      content\n      user_id\n      topic_id\n    )a\n\n    reply\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/repo.ex",
    "content": "defmodule Mipha.Repo do\n  use Ecto.Repo, otp_app: :mipha, adapter: Ecto.Adapters.Postgres\n\n  @doc \"\"\"\n  Dynamically loads the repository url from the\n  DATABASE_URL environment variable.\n  \"\"\"\n  def init(_, opts) do\n    {:ok, Keyword.put(opts, :url, System.get_env(\"DATABASE_URL\"))}\n  end\nend\n"
  },
  {
    "path": "lib/mipha/stars/star.ex",
    "content": "defmodule Mipha.Stars.Star do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.{Changeset, Query}\n\n  alias Mipha.{\n    Repo,\n    Topics.Topic,\n    Replies.Reply,\n    Accounts.User,\n    Stars.Star\n  }\n\n  @type t :: %Star{}\n\n  schema \"stars\" do\n    belongs_to :reply, Reply\n    belongs_to :user, User\n    belongs_to :topic, Topic\n\n    timestamps()\n  end\n\n  @doc \"\"\"\n  Filters the topic by star.\n  \"\"\"\n  @spec by_topic(Ecto.Queryable.t(), Topic.t()) :: Ecto.Query.t()\n  def by_topic(query \\\\ __MODULE__, %Topic{id: topic_id}),\n    do: from(s in query, where: s.topic_id == ^topic_id)\n\n  @doc \"\"\"\n  Filters the reply by star.\n  \"\"\"\n  @spec by_reply(Ecto.Queryable.t(), Reply.t()) :: Ecto.Query.t()\n  def by_reply(query \\\\ __MODULE__, %Reply{id: reply_id}),\n    do: from(s in query, where: s.reply_id == ^reply_id)\n\n  @doc \"\"\"\n  Preloads the reply of a topic.\n  \"\"\"\n  @spec preload_reply(t()) :: t()\n  def preload_reply(star), do: Repo.preload(star, :reply)\n\n  @doc \"\"\"\n  Preloads the reply of a topic.\n  \"\"\"\n  @spec preload_topic(t()) :: t()\n  def preload_topic(star), do: Repo.preload(star, :topic)\n\n  @doc false\n  def changeset(star, attrs) do\n    permitted_attrs = ~w(\n      user_id\n      reply_id\n      topic_id\n    )a\n\n    required_attrs = ~w(\n      user_id\n    )a\n\n    star\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/stars/stars.ex",
    "content": "defmodule Mipha.Stars do\n  @moduledoc \"\"\"\n  The Stars context.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias Ecto.Multi\n\n  alias Mipha.{\n    Repo,\n    Stars,\n    Topics,\n    Replies,\n    Accounts,\n    Notifications\n  }\n\n  alias Stars.Star\n  alias Topics.Topic\n  alias Replies.Reply\n  alias Accounts.User\n\n  @type starrable :: Topic.t() | Reply.t()\n\n  @doc \"\"\"\n  Returns the list of stars.\n\n  ## Examples\n\n      iex> list_stars()\n      [%Star{}, ...]\n\n  \"\"\"\n  def list_stars do\n    Repo.all(Star)\n  end\n\n  @doc \"\"\"\n  Gets a single star.\n\n  Raises `Ecto.NoResultsError` if the Star does not exist.\n\n  ## Examples\n\n      iex> get_star!(123)\n      %Star{}\n\n      iex> get_star!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_star!(id), do: Repo.get!(Star, id)\n\n  @doc \"\"\"\n  Creates a star.\n\n  ## Examples\n\n      iex> create_star(%{field: value})\n      {:ok, %Star{}}\n\n      iex> create_star(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_star(attrs \\\\ %{}) do\n    %Star{}\n    |> Star.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a star.\n\n  ## Examples\n\n      iex> update_star(star, %{field: new_value})\n      {:ok, %Star{}}\n\n      iex> update_star(star, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_star(%Star{} = star, attrs) do\n    star\n    |> Star.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Star.\n\n  ## Examples\n\n      iex> delete_star(star)\n      {:ok, %Star{}}\n\n      iex> delete_star(star)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  @spec delete_star(Star.t()) :: {:ok, Star.t()} | nil\n  def delete_star(%Star{} = star) do\n    Repo.delete(star)\n  end\n\n  @spec delete_star(Keyword.t()) :: {:ok, Star.t()} | nil\n  def delete_star(clauses) do\n    Multi.new()\n    |> Multi.delete(:star, get_star(clauses))\n    |> decrease_related_count(clauses)\n    |> Repo.transaction()\n  end\n\n  # unstar, decrease topic star_count\n  defp decrease_related_count(multi, user_id: _, topic_id: topic_id) when topic_id != \"\" do\n    update_topic_fn = fn _repo, %{star: star} ->\n      topic = starrable(star)\n      attrs = %{star_count: topic.star_count - 1}\n      Topics.update_topic(topic, attrs)\n    end\n\n    Multi.run(multi, :decrease_related_count, update_topic_fn)\n  end\n\n  # unstar, decrease reply star_count\n  defp decrease_related_count(multi, user_id: _, reply_id: reply_id) when reply_id != \"\" do\n    update_reply_fn = fn _repo, %{star: star} ->\n      reply = starrable(star)\n      attrs = %{star_count: reply.star_count - 1}\n      Replies.update_reply(reply, attrs)\n    end\n\n    Multi.run(multi, :decrease_related_count, update_reply_fn)\n  end\n\n  defp decrease_related_count(multi, _), do: multi\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking star changes.\n\n  ## Examples\n\n      iex> change_star(star)\n      %Ecto.Changeset{source: %Star{}}\n\n  \"\"\"\n  def change_star(%Star{} = star) do\n    Star.changeset(star, %{})\n  end\n\n  @doc \"\"\"\n  Gets a star.\n\n  ## Examples\n\n      iex> get_star(user_id: 123, topic_id: 123)\n      %Star{}\n\n      iex> get_star(user_id: 123, topic_id: 456)\n      nil\n\n  \"\"\"\n  @spec get_star(Keyword.t()) :: Star.t() | nil\n  def get_star(clauses) when length(clauses) == 2, do: Repo.get_by(Star, clauses)\n\n  @doc \"\"\"\n  Returns the starrable of the star.\n\n  ## Examples\n\n      iex> starrable(%Star{})\n      %Topic{}\n\n      iex> starrable(%Star{})\n      %Reply{}\n\n  \"\"\"\n  @spec starrable(Star.t()) :: Topic.t() | Reply.t()\n  def starrable(%Star{topic_id: topic_id} = star) when not is_nil(topic_id) do\n    star\n    |> Star.preload_topic()\n    |> Map.fetch!(:topic)\n  end\n\n  def starrable(%Star{reply_id: reply_id} = star) when not is_nil(reply_id) do\n    star\n    |> Star.preload_reply()\n    |> Map.fetch!(:reply)\n  end\n\n  @doc \"\"\"\n  Inserts a star.\n  \"\"\"\n  # @spec insert_star(map()) :: {:ok, Star.t()} | {:error, }\n  def insert_star(attrs) do\n    start_changeset = Star.changeset(%Star{}, attrs)\n\n    Multi.new()\n    |> Multi.insert(:star, start_changeset)\n    |> increase_related_count(attrs)\n    |> notify_author_of_starrable()\n    |> Repo.transaction()\n  end\n\n  # star, increase topic star_count\n  defp increase_related_count(multi, %{topic_id: topic_id}) when topic_id != \"\" do\n    update_topic_fn = fn _repo, %{star: star} ->\n      topic = starrable(star)\n      attrs = %{star_count: topic.star_count + 1}\n      Topics.update_topic(topic, attrs)\n    end\n\n    Multi.run(multi, :increase_related_count, update_topic_fn)\n  end\n\n  # star, increase reply star_count\n  defp increase_related_count(multi, %{reply_id: reply_id}) when reply_id != \"\" do\n    update_reply_fn = fn %{star: star} ->\n      reply = starrable(star)\n      attrs = %{star_count: reply.star_count + 1}\n      Replies.update_reply(reply, attrs)\n    end\n\n    Multi.run(multi, :increase_related_count, update_reply_fn)\n  end\n\n  defp increase_related_count(multi, _), do: multi\n\n  defp notify_author_of_starrable(multi) do\n    insert_notification_fn = fn _repo, %{star: star} ->\n      starrable = starrable(star)\n\n      author =\n        case starrable do\n          %Topic{} = topic -> Topics.author(topic)\n          %Reply{} = reply -> Replies.author(reply)\n        end\n\n      action =\n        case starrable do\n          %Topic{} -> \"topic_starred\"\n          %Reply{} -> \"reply_starred\"\n        end\n\n      notified_users = [author]\n\n      notification_attrs =\n        starrable\n        |> case do\n          %Topic{id: topic_id} -> %{topic_id: topic_id}\n          %Reply{id: reply_id} -> %{reply_id: reply_id}\n        end\n        |> Map.merge(%{\n          actor_id: author.id,\n          action: action,\n          notified_users: notified_users\n        })\n\n      case Notifications.insert_notification(notification_attrs) do\n        {:ok, %{notification: notification}} -> {:ok, notification}\n        {:error, _, reason, _} -> {:error, reason}\n      end\n    end\n\n    Multi.run(multi, :notification, insert_notification_fn)\n  end\n\n  # defp starrable_author(%Topic{} = topic), do: Topics.author(topic)\n  # defp starrable_author(%Reply{} = reply), do: Replies.author(reply)\n\n  @doc \"\"\"\n  Returns `true` if the user has starred the starrable.\n  `false` otherwise.\n\n  ## Examples\n\n      iex> has_starred?(user: %User{}, topic: %Topic{})\n      true\n\n      iex> has_starred?(user: %User{}, topic: %Topic{})\n      false\n\n  \"\"\"\n  @spec has_starred?(Keyword.t()) :: boolean\n  def has_starred?(clauses) do\n    %User{id: user_id} = Keyword.get(clauses, :user)\n\n    clauses =\n      clauses\n      |> get_starrable_from_clauses()\n      |> case do\n        %Topic{id: topic_id} -> [topic_id: topic_id]\n        %Reply{id: reply_id} -> [reply_id: reply_id]\n      end\n      |> Keyword.put(:user_id, user_id)\n\n    !!get_star(clauses)\n  end\n\n  @spec get_starrable_from_clauses(Keyword.t()) :: starrable()\n  defp get_starrable_from_clauses(clauses) do\n    cond do\n      Keyword.has_key?(clauses, :topic) -> Keyword.get(clauses, :topic)\n      Keyword.has_key?(clauses, :reply) -> Keyword.get(clauses, :reply)\n      true -> Star\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha/token.ex",
    "content": "defmodule Mipha.Token do\n  @moduledoc false\n\n  alias Mipha.Accounts.User\n\n  @verification_salt \"mipha\"\n\n  def generate_token(%User{id: user_id}) do\n    Phoenix.Token.sign(MiphaWeb.Endpoint, @verification_salt, user_id)\n  end\n\n  def verify_token(token) do\n    max_age = 86_400\n    Phoenix.Token.verify(MiphaWeb.Endpoint, @verification_salt, token, max_age: max_age)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/topics/node.ex",
    "content": "defmodule Mipha.Topics.Node do\n  @moduledoc false\n  use Ecto.Schema\n  import Ecto.{Changeset, Query}\n\n  alias Mipha.Repo\n  alias Mipha.Topics.{Topic, Node}\n\n  @type t :: %Node{}\n\n  schema \"nodes\" do\n    field :name, :string\n    field :position, :integer\n    field :summary, :string\n\n    belongs_to :parent, Node, foreign_key: :parent_id\n\n    has_many :topics, Topic\n    has_many :children, Node, foreign_key: :parent_id\n\n    timestamps()\n  end\n\n  @doc \"\"\"\n  Returns the children node.\n  \"\"\"\n  @spec is_child(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def is_child(query \\\\ __MODULE__), do: from(q in query, where: not is_nil(q.parent_id))\n\n  @doc \"\"\"\n  Returns the parent of node.\n  \"\"\"\n  @spec is_parent(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def is_parent(query \\\\ __MODULE__), do: from(q in query, where: is_nil(q.parent_id))\n\n  @doc \"\"\"\n  Preloads the children of a node.\n  \"\"\"\n  @spec preload_children(t()) :: t()\n  def preload_children(node), do: Repo.preload(node, :children)\n\n  @doc false\n  def changeset(node, attrs) do\n    permitted_attrs = ~w(\n      name\n      summary\n      position\n      parent_id\n    )a\n\n    required_attrs = ~w(\n      name\n    )a\n\n    node\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/topics/queries.ex",
    "content": "defmodule Mipha.Topics.Queries do\n  @moduledoc false\n\n  import Ecto.Query\n  alias Mipha.Topics.{Topic, Node}\n\n  @doc \"\"\"\n  Returns the list topics.\n  \"\"\"\n  @spec list_topics :: Ecto.Query.t()\n  def list_topics, do: Topic\n\n  @doc \"\"\"\n  Returns the list nodes.\n  \"\"\"\n  @spec list_nodes :: Ecto.Query.t()\n  def list_nodes, do: Node\n\n  @doc \"\"\"\n  Returns the the type equal job topics.\n  \"\"\"\n  @spec job_topics :: Ecto.Query.t()\n  def job_topics do\n    Topic.job()\n    |> preload([:user, :node, :last_reply_user])\n  end\n\n  @doc \"\"\"\n  Filter the list of topics.\n\n  ## Examples\n\n      iex> cond_topics()\n      Ecto.Query.t()\n\n      iex> cond_topics(type: :educational)\n      Ecto.Query.t()\n\n      iex> cond_topics(node: %Node{})\n      Ecto.Query.t()\n\n      iex> cond_topics(user: %User{})\n      Ecto.Query.t()\n\n  \"\"\"\n  @spec cond_topics(Keyword.t()) :: Ecto.Query.t()\n  def cond_topics(opts) do\n    opts\n    |> filter_from_clauses\n    |> Topic.base_order()\n    |> preload([:user, :node, :last_reply_user])\n  end\n\n  defp filter_from_clauses(opts), do: do_filter_from_clauses(opts)\n\n  defp do_filter_from_clauses(type: :educational), do: Topic.educational()\n  defp do_filter_from_clauses(type: :featured), do: Topic.featured()\n  defp do_filter_from_clauses(type: :no_reply), do: Topic.no_reply()\n  defp do_filter_from_clauses(type: :popular), do: Topic.popular()\n  defp do_filter_from_clauses(node: node), do: Topic.by_node(node)\n  defp do_filter_from_clauses(user: user), do: Topic.by_user(user)\n  defp do_filter_from_clauses(user_ids: user_ids), do: Topic.by_user_ids(user_ids)\n  defp do_filter_from_clauses(_), do: Topic\nend\n"
  },
  {
    "path": "lib/mipha/topics/topic.ex",
    "content": "defmodule Mipha.Topics.Topic do\n  @moduledoc false\n\n  use Ecto.Schema\n  import Ecto.{Changeset, Query}\n  import EctoEnum, only: [defenum: 3]\n\n  alias Mipha.{\n    Repo,\n    Accounts.User,\n    Replies.Reply,\n    Stars.Star,\n    Collections.Collection\n  }\n\n  alias Mipha.Topics.{Topic, Node}\n\n  @type t :: %Topic{}\n\n  defenum(TopicType, :topic_type, [\n    :normal,\n    :featured,\n    :educational,\n    :job\n  ])\n\n  schema \"topics\" do\n    field :title, :string\n    field :body, :string\n    field :type, TopicType\n    field :closed_at, :naive_datetime\n    field :replied_at, :naive_datetime\n    field :suggested_at, :naive_datetime\n    field :reply_count, :integer, default: 0\n    field :visit_count, :integer, default: 0\n    field :star_count, :integer, default: 0\n\n    belongs_to :user, User\n    belongs_to :node, Node\n    belongs_to :last_reply, Reply, foreign_key: :last_reply_id\n    belongs_to :last_reply_user, User, foreign_key: :last_reply_user_id\n\n    has_many :replies, Reply\n    has_many :stars, Star\n    has_many :collections, Collection, on_delete: :delete_all\n\n    timestamps()\n  end\n\n  @doc \"\"\"\n  Returns the job of topic.\n  \"\"\"\n  @spec job(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def job(query \\\\ __MODULE__),\n    do: where(query, [..., t], t.type == ^:job)\n\n  @doc \"\"\"\n  Filters the featured of topics.\n  \"\"\"\n  @spec featured(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def featured(query \\\\ __MODULE__),\n    do: where(query, [..., t], t.type == ^:featured)\n\n  @doc \"\"\"\n  Filters the educational of topics.\n  \"\"\"\n  @spec educational(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def educational(query \\\\ __MODULE__),\n    do: where(query, [..., t], t.type == ^:educational)\n\n  @doc \"\"\"\n  Filters the no_reply of topics.\n  \"\"\"\n  @spec no_reply(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def no_reply(query \\\\ __MODULE__),\n    do: where(query, [..., t], t.reply_count == 0)\n\n  @doc \"\"\"\n  Filters the popular of topics.\n  \"\"\"\n  @spec popular(Ecto.Queryable.t()) :: Ecto.Query.t()\n  def popular(query \\\\ __MODULE__),\n    do: where(query, [..., t], t.reply_count >= 10)\n\n  @doc \"\"\"\n  Filters the node of topics.\n  \"\"\"\n  @spec by_node(Ecto.Queryable.t(), Node.t()) :: Ecto.Query.t()\n  def by_node(query \\\\ __MODULE__, %Node{id: node_id}),\n    do: where(query, [..., t], t.node_id == ^node_id)\n\n  @doc \"\"\"\n  Filters the user of topics.\n  \"\"\"\n  @spec by_user(Ecto.Queryable.t(), User.t()) :: Ecto.Query.t()\n  def by_user(query \\\\ __MODULE__, %User{id: user_id}),\n    do: where(query, [..., t], t.user_id == ^user_id)\n\n  @doc \"\"\"\n  Filters the user of topics.\n  \"\"\"\n  @spec by_user_ids(Ecto.Queryable.t(), List.t()) :: Ecto.Query.t()\n  def by_user_ids(query \\\\ __MODULE__, list),\n    do: where(query, [..., t], t.user_id in ^list)\n\n  @doc \"\"\"\n  Returns nearly 10 topics\n  \"\"\"\n  @spec recent(t()) :: t()\n  def recent(query \\\\ __MODULE__),\n    do: from(t in query, order_by: [desc: t.updated_at], limit: 10)\n\n  @doc \"\"\"\n  Returns the default sort of the topic list, according to suggested_at && updated_at\n  \"\"\"\n  @spec base_order(t()) :: t()\n  def base_order(query \\\\ __MODULE__),\n    do: from(t in query, order_by: [asc: t.suggested_at], order_by: [desc: t.updated_at])\n\n  @doc \"\"\"\n  Preloads the user of a topic.\n  \"\"\"\n  @spec preload_user(t()) :: t()\n  def preload_user(topic), do: Repo.preload(topic, [:user, :last_reply_user])\n\n  @doc \"\"\"\n  Preloads the reply of a topic.\n  \"\"\"\n  @spec preload_replies(t()) :: t()\n  def preload_replies(topic), do: Repo.preload(topic, :replies)\n\n  @doc \"\"\"\n  Preloads the reply of a topic.\n  \"\"\"\n  @spec preload_node(t()) :: t()\n  def preload_node(topic), do: Repo.preload(topic, :node)\n\n  @doc \"\"\"\n  Preloads all of a topic.\n  \"\"\"\n  @spec preload_all(t()) :: t()\n  def preload_all(topic) do\n    topic\n    |> preload_replies\n    |> preload_user\n    |> preload_node\n  end\n\n  @doc \"\"\"\n  Topic increment/decrement count\n  \"\"\"\n  def counter(%Topic{id: topic_id}, :inc, :visit_count) do\n    Topic\n    |> where([..., t], t.id == ^topic_id)\n    |> Repo.update_all(inc: [visit_count: 1])\n  end\n\n  def counter(%Topic{id: topic_id}, :inc, :reply_count) do\n    Topic\n    |> where([..., t], t.id == ^topic_id)\n    |> Repo.update_all(inc: [reply_count: 1])\n  end\n\n  def counter(%Topic{id: topic_id}, :dec, :reply_count) do\n    Topic\n    |> where([..., t], t.id == ^topic_id)\n    |> Repo.update_all(inc: [reply_count: -1])\n  end\n\n  @doc false\n  def changeset(topic, attrs) do\n    permitted_attrs = ~w(\n      title\n      body\n      closed_at\n      user_id\n      type\n      node_id\n      visit_count\n      reply_count\n      star_count\n      last_reply_id\n      last_reply_user_id\n      replied_at\n      suggested_at\n    )a\n\n    required_attrs = ~w(\n      title\n      body\n      node_id\n      user_id\n    )a\n\n    topic\n    |> cast(attrs, permitted_attrs)\n    |> validate_required(required_attrs)\n  end\nend\n"
  },
  {
    "path": "lib/mipha/topics/topics.ex",
    "content": "defmodule Mipha.Topics do\n  @moduledoc \"\"\"\n  The Topics context.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias Ecto.Multi\n\n  alias Mipha.{\n    Repo,\n    Accounts,\n    Notifications\n  }\n\n  alias Mipha.Topics.Topic\n  alias Accounts.User\n  alias Mipha.Follows.Follow\n\n  @username_regex ~r{@([A-Za-z0-9]+)}\n\n  @doc \"\"\"\n  Returns the list of topics.\n\n  ## Examples\n\n      iex> list_topics()\n      [%Topic{}, ...]\n\n  \"\"\"\n  def list_topics do\n    Topic\n    |> Repo.all()\n  end\n\n  @doc \"\"\"\n  Gets a single topic.\n\n  Raises `Ecto.NoResultsError` if the Topic does not exist.\n\n  ## Examples\n\n      iex> get_topic!(123)\n      %Topic{}\n\n      iex> get_topic!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_topic!(id) do\n    Topic\n    |> Repo.get!(id)\n    |> Repo.preload([:node, :user, :last_reply_user, [replies: [:user, [parent: :user]]]])\n  end\n\n  @doc \"\"\"\n  Creates a topic.\n\n  ## Examples\n\n      iex> create_topic(%{field: value})\n      {:ok, %Topic{}}\n\n      iex> create_topic(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_topic(attrs \\\\ %{}) do\n    %Topic{}\n    |> Topic.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a topic.\n\n  ## Examples\n\n      iex> update_topic(topic, %{field: new_value})\n      {:ok, %Topic{}}\n\n      iex> update_topic(topic, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_topic(%Topic{} = topic, attrs) do\n    topic\n    |> Topic.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Topic.\n\n  ## Examples\n\n      iex> delete_topic(topic)\n      {:ok, %Topic{}}\n\n      iex> delete_topic(topic)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_topic(%Topic{} = topic) do\n    Repo.delete(topic)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking topic changes.\n\n  ## Examples\n\n      iex> change_topic(topic)\n      %Ecto.Changeset{source: %Topic{}}\n\n  \"\"\"\n  @spec change_topic(Topic.t()) :: Ecto.Changeset.t()\n  def change_topic(%Topic{} = topic \\\\ %Topic{}), do: Topic.changeset(topic, %{})\n\n  @doc \"\"\"\n  Increment topic visit count.\n  \"\"\"\n  def topic_visit_counter(%Topic{} = topic), do: Topic.counter(topic, :inc, :visit_count)\n\n  @doc \"\"\"\n  Inserts a topic.\n\n  ## Examples\n\n      iex> insert_topic(%User{}, %{field: value})\n      {:ok, %Topic{}}\n\n      iex> insert_topic(%User{}, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  @spec insert_topic(User.t(), map()) :: {:ok, Topic.t()} | {:error, Ecto.Changeset.t()}\n  def insert_topic(user, attrs \\\\ %{}) do\n    attrs = attrs |> Map.put(\"user_id\", user.id)\n    topic_changeset = Topic.changeset(%Topic{}, attrs)\n\n    Multi.new()\n    |> Multi.insert(:topic, topic_changeset)\n    |> maybe_notify_users_of_new_topic()\n    |> maybe_notify_mention_users_of_new_topic(attrs)\n    |> Repo.transaction()\n  end\n\n  defp maybe_notify_users_of_new_topic(multi) do\n    insert_notification_fn = fn _repo, %{topic: topic} ->\n      # 获取关注话题作者的 follower.\n      notified_users = notifiable_users_of_topic(topic)\n\n      attrs = %{\n        actor_id: topic.user_id,\n        action: \"topic_added\",\n        topic_id: topic.id,\n        notified_users: notified_users\n      }\n\n      case Notifications.insert_notification(attrs) do\n        {:ok, %{notification: notification}} -> {:ok, notification}\n        {:error, _, reason, _} -> {:error, reason}\n      end\n    end\n\n    Multi.run(multi, :notify_users_of_new_topic, insert_notification_fn)\n  end\n\n  # 通知被 @ 的用户\n  def maybe_notify_mention_users_of_new_topic(multi, attrs) do\n    insert_notification_fn = fn _repo, %{topic: topic} ->\n      notified_users =\n        @username_regex\n        |> Regex.scan(attrs[\"body\"])\n        |> Enum.map(fn [_, match] -> Accounts.get_user_by_username(match) end)\n        |> Enum.filter(&(not is_nil(&1)))\n\n      attrs = %{\n        actor_id: topic.user_id,\n        action: \"topic_mentioned\",\n        topic_id: topic.id,\n        notified_users: notified_users\n      }\n\n      case Notifications.insert_notification(attrs) do\n        {:ok, %{notification: notification}} -> {:ok, notification}\n        {:error, _, reason, _} -> {:error, reason}\n      end\n    end\n\n    Multi.run(multi, :notify_mention_users_of_new_topic, insert_notification_fn)\n  end\n\n  # 获取关注话题作者的 follower.\n  def notifiable_users_of_topic(%Topic{} = topic) do\n    query =\n      from u in User,\n        join: f in Follow,\n        on: f.follower_id == u.id,\n        where: f.user_id == ^topic.user_id\n\n    Repo.all(query)\n  end\n\n  @doc \"\"\"\n  Returns the featured of topics.\n\n  ## Examples\n\n      iex> list_topics()\n      [%Topic{}, ...]\n\n  \"\"\"\n  def list_featured_topics do\n    Topic.featured()\n    |> Repo.all()\n    |> Repo.preload([:node, :user, :last_reply_user])\n  end\n\n  @doc \"\"\"\n  Return topic count of a owner(user).\n  \"\"\"\n  @spec get_topic_count(User.t()) :: non_neg_integer()\n  def get_topic_count(%User{} = user) do\n    Topic\n    |> Topic.by_user(user)\n    |> Repo.aggregate(:count, :id)\n  end\n\n  @doc \"\"\"\n  获取全部 topic 个数\n  \"\"\"\n  @spec get_total_topic_count :: non_neg_integer()\n  def get_total_topic_count do\n    Topic\n    |> Repo.aggregate(:count, :id)\n  end\n\n  @doc \"\"\"\n  Return the recent of topics.\n  \"\"\"\n  @spec recent_topics(User.t()) :: [Topic.t()] | nil\n  def recent_topics(%User{} = user) do\n    user\n    |> Topic.by_user()\n    |> Topic.recent()\n    |> Repo.all()\n    |> Repo.preload([:node, :user, :last_reply_user])\n  end\n\n  @doc \"\"\"\n  获取话题作者\n\n  ## Example\n\n      iex> author(%Topic{})\n      %User{}\n\n  \"\"\"\n  @spec author(Topic.t()) :: User.t()\n  def author(%Topic{} = topic) do\n    topic\n    |> Topic.preload_user()\n    |> Map.fetch!(:user)\n  end\n\n  alias Mipha.Topics.Node\n\n  @doc \"\"\"\n  Returns the list of nodes.\n\n  ## Examples\n\n      iex> list_nodes()\n      [%Node{}, ...]\n\n  \"\"\"\n  def list_nodes do\n    Repo.all(Node)\n  end\n\n  @doc \"\"\"\n  Gets a single node.\n\n  Raises `Ecto.NoResultsError` if the Node does not exist.\n\n  ## Examples\n\n      iex> get_node!(123)\n      %Node{}\n\n      iex> get_node!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_node!(id), do: Repo.get!(Node, id)\n\n  @doc \"\"\"\n  Creates a node.\n\n  ## Examples\n\n      iex> create_node(%{field: value})\n      {:ok, %Node{}}\n\n      iex> create_node(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_node(attrs \\\\ %{}) do\n    %Node{}\n    |> Node.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a node.\n\n  ## Examples\n\n      iex> update_node(node, %{field: new_value})\n      {:ok, %Node{}}\n\n      iex> update_node(node, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_node(%Node{} = node, attrs) do\n    node\n    |> Node.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a Node.\n\n  ## Examples\n\n      iex> delete_node(node)\n      {:ok, %Node{}}\n\n      iex> delete_node(node)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_node(%Node{} = node) do\n    Repo.delete(node)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking node changes.\n\n  ## Examples\n\n      iex> change_node(node)\n      %Ecto.Changeset{source: %Node{}}\n\n  \"\"\"\n  @spec change_node(Node.t()) :: Ecto.Changeset.t()\n  def change_node(%Node{} = node \\\\ %Node{}), do: Node.changeset(node, %{})\n\n  @doc \"\"\"\n  Returns the parent of nodes\n  \"\"\"\n  @spec list_parent_nodes :: [Node.t()] | nil\n  def list_parent_nodes do\n    Node.is_parent()\n    |> Repo.all()\n    |> Node.preload_children()\n  end\nend\n"
  },
  {
    "path": "lib/mipha/utils/store.ex",
    "content": "defmodule Mipha.Utils.Store do\n  @moduledoc \"\"\"\n  文件储存\n  \"\"\"\n\n  alias Cachex\n\n  @doc \"\"\"\n  读取文件信息\n\n  ## Example\n\n      iex> Mipha.Utils.Store.get(a)\n      {:ok, \"b\"}\n\n  \"\"\"\n  def get(cache_key) do\n    Cachex.get(:app_cache, cache_key)\n  end\n\n  def get!(cache_key) do\n    Cachex.get!(:app_cache, cache_key)\n  end\n\n  @doc \"\"\"\n  储存信息\n  \"\"\"\n  def put(cache_key, cache_value) do\n    Cachex.put(:app_cache, cache_key, cache_value)\n  end\n\n  def put!(cache_key, cache_value) do\n    Cachex.put!(:app_cache, cache_key, cache_value)\n  end\nend\n"
  },
  {
    "path": "lib/mipha.ex",
    "content": "defmodule Mipha do\n  @moduledoc \"\"\"\n  Mipha keeps the contexts that define your domain\n  and business logic.\n\n  Contexts are also responsible for managing your data, regardless\n  if it comes from the database, an external API or others.\n  \"\"\"\nend\n"
  },
  {
    "path": "lib/mipha_web/channels/presence.ex",
    "content": "defmodule MiphaWeb.Presence do\n  @moduledoc \"\"\"\n  Provides presence tracking to channels and processes.\n\n  See the [`Phoenix.Presence`](http://hexdocs.pm/phoenix/Phoenix.Presence.html)\n  docs for more details.\n\n  ## Usage\n\n  Presences can be tracked in your channel after joining:\n\n      defmodule Mipha.MyChannel do\n        use MiphaWeb, :channel\n        alias Mipha.Presence\n\n        def join(\"some:topic\", _params, socket) do\n          send(self, :after_join)\n          {:ok, assign(socket, :user_id, ...)}\n        end\n\n        def handle_info(:after_join, socket) do\n          push socket, \"presence_state\", Presence.list(socket)\n          {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{\n            online_at: inspect(System.system_time(:seconds))\n          })\n          {:noreply, socket}\n        end\n      end\n\n  In the example above, `Presence.track` is used to register this\n  channel's process as a presence for the socket's user ID, with\n  a map of metadata. Next, the current presence list for\n  the socket's topic is pushed to the client as a `\"presence_state\"` event.\n\n  Finally, a diff of presence join and leave events will be sent to the\n  client as they happen in real-time with the \"presence_diff\" event.\n  See `Phoenix.Presence.list/2` for details on the presence datastructure.\n\n  ## Fetching Presence Information\n\n  The `fetch/2` callback is triggered when using `list/1`\n  and serves as a mechanism to fetch presence information a single time,\n  before broadcasting the information to all channel subscribers.\n  This prevents N query problems and gives you a single place to group\n  isolated data fetching to extend presence metadata.\n\n  The function receives a topic and map of presences and must return a\n  map of data matching the Presence datastructure:\n\n      %{\"123\" => %{metas: [%{status: \"away\", phx_ref: ...}],\n        \"456\" => %{metas: [%{status: \"online\", phx_ref: ...}]}\n\n  The `:metas` key must be kept, but you can extend the map of information\n  to include any additional information. For example:\n\n      def fetch(_topic, entries) do\n        users = entries |> Map.keys() |> Accounts.get_users_map(entries)\n        # => %{\"123\" => %{name: \"User 123\"}, \"456\" => %{name: nil}}\n\n        for {key, %{metas: metas}} <- entries, into: %{} do\n          {key, %{metas: metas, user: users[key]}}\n        end\n      end\n\n  The function above fetches all users from the database who\n  have registered presences for the given topic. The fetched\n  information is then extended with a `:user` key of the user's\n  information, while maintaining the required `:metas` field from the\n  original presence data.\n  \"\"\"\n  use Phoenix.Presence,\n    otp_app: :mipha,\n    pubsub_server: Mipha.PubSub\nend\n"
  },
  {
    "path": "lib/mipha_web/channels/room_channel.ex",
    "content": "defmodule MiphaWeb.RoomChannel do\n  @moduledoc \"\"\"\n  Room Channel\n  \"\"\"\n\n  use MiphaWeb, :channel\n\n  alias MiphaWeb.Presence\n  alias Mipha.Repo\n  alias Mipha.Accounts.User\n\n  def join(\"room:lobby\", payload, socket) do\n    if authorized?(payload) do\n      send(self(), :after_join)\n      {:ok, %{channel: \"room:lobby\"}, socket}\n    else\n      {:error, %{reason: \"unauthorized\"}}\n    end\n  end\n\n  # Channels can be used in a request/response fashion\n  # by sending replies to requests from the client\n  def handle_in(\"ping\", payload, socket) do\n    {:reply, {:ok, payload}, socket}\n  end\n\n  # It is also common to receive messages from the client and\n  # broadcast to everyone in the current topic (room:lobby).\n  def handle_in(\"shout\", payload, socket) do\n    broadcast(socket, \"shout\", payload)\n    {:noreply, socket}\n  end\n\n  def handle_info(:after_join, socket) do\n    push(socket, \"presence_state\", Presence.list(socket))\n\n    user = Repo.get(User, socket.assigns[:user_id])\n\n    {:ok, _} =\n      Presence.track(socket, \"user:#{user.id}\", %{\n        user_id: user.id,\n        username: user.username\n      })\n\n    {:noreply, socket}\n  end\n\n  # Add authorization logic here as required.\n  defp authorized?(_payload) do\n    true\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/channels/topic_channel.ex",
    "content": "defmodule MiphaWeb.TopicChannel do\n  @moduledoc \"\"\"\n  Topic Channel\n  \"\"\"\n\n  use MiphaWeb, :channel\n\n  def join(\"topic:\" <> topic_id, payload, socket) do\n    if authorized?(payload) do\n      {:ok, %{channel: \"topic:#{topic_id}\"}, assign(socket, :topic_id, topic_id)}\n    else\n      {:error, %{reason: \"unauthorized\"}}\n    end\n  end\n\n  # Channels can be used in a request/response fashion\n  # by sending replies to requests from the client\n  def handle_in(\"ping\", payload, socket) do\n    {:reply, {:ok, payload}, socket}\n  end\n\n  # It is also common to receive messages from the client and\n  # broadcast to everyone in the current topic (topic:lobby).\n  def handle_in(\"shout\", payload, socket) do\n    broadcast(socket, \"shout\", payload)\n    {:noreply, socket}\n  end\n\n  # Add authorization logic here as required.\n  defp authorized?(_payload) do\n    true\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/channels/user_socket.ex",
    "content": "defmodule MiphaWeb.UserSocket do\n  use Phoenix.Socket\n\n  ## Channels\n  # channel \"room:*\", MiphaWeb.RoomChannel\n  channel(\"topic:*\", MiphaWeb.TopicChannel)\n\n  ## Transports\n  # transport(:websocket, Phoenix.Transports.WebSocket, timeout: 45_000)\n  # transport :longpoll, Phoenix.Transports.LongPoll\n\n  # Socket params are passed from the client and can\n  # be used to verify and authenticate a user. After\n  # verification, you can put default assigns into\n  # the socket that will be set for all channels, ie\n  #\n  #     {:ok, assign(socket, :user_id, verified_user_id)}\n  #\n  # To deny connection, return `:error`.\n  #\n  # See `Phoenix.Token` documentation for examples in\n  # performing token verification on connect.\n\n  def connect(%{\"token\" => token}, socket) do\n    case Phoenix.Token.verify(socket, \"user socket\", token, max_age: 86_400) do\n      {:ok, user_id} ->\n        {:ok, assign(socket, :user_id, user_id)}\n\n      {:error, _reason} ->\n        :error\n    end\n  end\n\n  def connect(_params, _socket), do: :error\n\n  # Socket id's are topics that allow you to identify all sockets for a given user:\n  #\n  #     def id(socket), do: \"user_socket:#{socket.assigns.user_id}\"\n  #\n  # Would allow you to broadcast a \"disconnect\" event and terminate\n  # all active sockets and channels for a given user:\n  #\n  #     MiphaWeb.Endpoint.broadcast(\"user_socket:#{user.id}\", \"disconnect\", %{})\n  #\n  # Returning `nil` makes this socket anonymous.\n  def id(_socket), do: nil\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/admin/company_controller.ex",
    "content": "defmodule MiphaWeb.Admin.CompanyController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Accounts\n  alias Mipha.Accounts.Queries\n\n  def index(conn, params) do\n    result = Queries.list_companies() |> Turbo.Ecto.turbo(params)\n    render(conn, :index, companies: result.datas, paginate: result.paginate)\n  end\n\n  def delete(conn, %{\"id\" => id}) do\n    company = Accounts.get_company!(id)\n    {:ok, _company} = Accounts.delete_company(company)\n\n    conn\n    |> put_flash(:info, \"Company deleted successfully.\")\n    |> redirect(to: admin_company_path(conn, :index))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/admin/node_controller.ex",
    "content": "defmodule MiphaWeb.Admin.NodeController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Topics\n  alias Mipha.Topics.Queries\n\n  def index(conn, params) do\n    result = Queries.list_nodes() |> Turbo.Ecto.turbo(params)\n    render(conn, :index, nodes: result.datas, paginate: result.paginate)\n  end\n\n  def new(conn, _params) do\n    changeset = Topics.change_node()\n    render(conn, \"new.html\", changeset: changeset)\n  end\n\n  def create(conn, %{\"node\" => node_params}) do\n    case Topics.create_node(node_params) do\n      {:ok, node} ->\n        conn\n        |> put_flash(:info, \"Node created successfully.\")\n        |> redirect(to: admin_node_path(conn, :show, node))\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        render(conn, \"new.html\", changeset: changeset)\n    end\n  end\n\n  def show(conn, %{\"id\" => id}) do\n    node = Topics.get_node!(id)\n    render(conn, \"show.html\", node: node)\n  end\n\n  def edit(conn, %{\"id\" => id}) do\n    node = Topics.get_node!(id)\n    changeset = Topics.change_node(node)\n    render(conn, \"edit.html\", node: node, changeset: changeset)\n  end\n\n  def update(conn, %{\"id\" => id, \"node\" => node_params}) do\n    node = Topics.get_node!(id)\n\n    case Topics.update_node(node, node_params) do\n      {:ok, node} ->\n        conn\n        |> put_flash(:info, \"Node updated successfully.\")\n        |> redirect(to: admin_node_path(conn, :show, node))\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        render(conn, \"edit.html\", node: node, changeset: changeset)\n    end\n  end\n\n  def delete(conn, %{\"id\" => id}) do\n    node = Topics.get_node!(id)\n    {:ok, _node} = Topics.delete_node(node)\n\n    conn\n    |> put_flash(:info, \"Node deleted successfully.\")\n    |> redirect(to: admin_node_path(conn, :index))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/admin/notification_controller.ex",
    "content": "defmodule MiphaWeb.Admin.NotificationController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Notifications\n\n  def index(conn, _params) do\n    notifications = Notifications.list_notifications()\n    render(conn, \"index.html\", notifications: notifications)\n  end\n\n  def show(conn, %{\"id\" => id}) do\n    notification = Notifications.get_notification!(id)\n    render(conn, \"show.html\", notification: notification)\n  end\n\n  def delete(conn, %{\"id\" => id}) do\n    notification = Notifications.get_notification!(id)\n    {:ok, _notification} = Notifications.delete_notification(notification)\n\n    conn\n    |> put_flash(:info, \"Notification deleted successfully.\")\n    |> redirect(to: admin_notification_path(conn, :index))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/admin/page_controller.ex",
    "content": "defmodule MiphaWeb.Admin.PageController do\n  use MiphaWeb, :controller\n\n  def index(conn, _) do\n    render(conn, :index)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/admin/reply_controller.ex",
    "content": "defmodule MiphaWeb.Admin.ReplyController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Replies\n  alias Mipha.Replies.Queries\n\n  def index(conn, params) do\n    result = Queries.list_replies() |> Turbo.Ecto.turbo(params)\n    render(conn, :index, replies: result.datas, paginate: result.paginate)\n  end\n\n  def show(conn, %{\"id\" => id}) do\n    reply = Replies.get_reply!(id)\n    render(conn, \"show.html\", reply: reply)\n  end\n\n  def delete(conn, %{\"id\" => id}) do\n    reply = Replies.get_reply!(id)\n    {:ok, _reply} = Replies.delete_reply(reply)\n\n    conn\n    |> put_flash(:info, \"Reply deleted successfully.\")\n    |> redirect(to: admin_reply_path(conn, :index))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/admin/team_controller.ex",
    "content": "defmodule MiphaWeb.Admin.TeamController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Accounts\n  alias Mipha.Accounts.Queries\n\n  def index(conn, params) do\n    result = Queries.list_teams() |> Turbo.Ecto.turbo(params)\n    render(conn, :index, teams: result.datas, paginate: result.paginate)\n  end\n\n  def delete(conn, %{\"id\" => id}) do\n    team = Accounts.get_team!(id)\n    {:ok, _team} = Accounts.delete_team(team)\n\n    conn\n    |> put_flash(:info, \"Team deleted successfully.\")\n    |> redirect(to: admin_team_path(conn, :index))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/admin/topic_controller.ex",
    "content": "defmodule MiphaWeb.Admin.TopicController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Topics.Queries\n\n  def index(conn, params) do\n    result = Queries.list_topics() |> Turbo.Ecto.turbo(params)\n    render(conn, :index, topics: result.datas, paginate: result.paginate)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/admin/user_controller.ex",
    "content": "defmodule MiphaWeb.Admin.UserController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Accounts\n  alias Mipha.Accounts.Queries\n\n  def index(conn, params) do\n    result = Queries.list_users() |> Turbo.Ecto.turbo(params)\n    render(conn, :index, users: result.datas, paginate: result.paginate)\n  end\n\n  def delete(conn, %{\"id\" => id}) do\n    user = Accounts.get_user!(id)\n    {:ok, _user} = Accounts.delete_user(user)\n\n    conn\n    |> put_flash(:info, \"User deleted successfully.\")\n    |> redirect(to: admin_user_path(conn, :index))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/auth_controller.ex",
    "content": "defmodule MiphaWeb.AuthController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Accounts\n\n  plug :authorized_user when action not in ~w(delete)a\n\n  def login(conn, _params) do\n    changeset = Accounts.user_login_changeset()\n    render(conn, :login, changeset: changeset)\n  end\n\n  def delete(conn, _params) do\n    conn\n    |> put_flash(:info, gettext(\"Sign out successful\"))\n    |> configure_session(drop: true)\n    |> redirect(to: \"/\")\n  end\n\n  alias Ueberauth.Strategy.Helpers\n\n  def request(conn, %{\"provider\" => \"identity\"}) do\n    changeset = Accounts.user_register_changeset()\n    render(conn, :request, callback_url: Helpers.callback_url(conn), changeset: changeset)\n  end\n\n  def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do\n    conn\n    |> put_flash(:danger, gettext(\"Failed to authenticate.\"))\n    |> redirect(to: \"/\")\n  end\n\n  def callback(%{assigns: %{ueberauth_auth: auth}} = conn, params) do\n    case auth.provider do\n      :identity ->\n        attrs = %{\n          login: params[\"user\"][\"login\"],\n          password: params[\"user\"][\"password\"]\n        }\n\n        case Accounts.authenticate(attrs) do\n          {:ok, user} ->\n            conn |> ok_login(user)\n\n          {:error, %Ecto.Changeset{} = changeset} ->\n            conn\n            |> put_flash(:danger, \"Login Unsuccessful\")\n            |> render(:login, callback_url: Helpers.callback_url(conn), changeset: changeset)\n\n          {:error, reason} ->\n            changeset = Accounts.user_login_changeset()\n\n            conn\n            |> put_flash(:danger, reason)\n            |> render(:login, callback_url: Helpers.callback_url(conn), changeset: changeset)\n        end\n\n      :github ->\n        with %{name: name, nickname: nickname, email: email} <- auth.info do\n          case Accounts.login_or_register_from_github(%{\n                 name: name,\n                 nickname: nickname,\n                 email: email\n               }) do\n            {:ok, user} ->\n              conn\n              |> ok_login(user)\n\n            {:error, reason} ->\n              conn\n              |> put_flash(:error, reason)\n              |> redirect(to: \"/\")\n          end\n        end\n    end\n  end\n\n  defp ok_login(conn, user) do\n    conn\n    |> put_flash(:info, gettext(\"Login successfully\"))\n    |> put_session(:current_user, user.id)\n    |> redirect(to: \"/\")\n  end\n\n  # if user login,can't reach this controller.\n  defp authorized_user(conn, _) do\n    if current_user(conn) do\n      conn\n      |> put_flash(:danger, gettext(\"You has logged in\"))\n      |> redirect(to: \"/\")\n    else\n      conn\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/callback_controller.ex",
    "content": "defmodule MiphaWeb.CallbackController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Qiniu\n\n  @doc \"\"\"\n  Upload image callback method.\n  \"\"\"\n  def qiniu(conn, %{\"file\" => file_params}) do\n    with %HTTPoison.Response{status_code: 200, body: body} <- Qiniu.upload(file_params.path) do\n      conn\n      |> json(%{qn_url: Qiniu.q_url(body[\"key\"])})\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/company_controller.ex",
    "content": "defmodule MiphaWeb.CompanyController do\n  use MiphaWeb, :controller\n\n  def action(conn, _) do\n    render(conn, action_name(conn))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/location_controller.ex",
    "content": "defmodule MiphaWeb.LocationController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Accounts\n\n  def index(conn, _) do\n    render(conn, :index)\n  end\n\n  def show(conn, %{\"id\" => id}) do\n    location = Accounts.get_location!(id)\n    render(conn, :show, location: location)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/notification_controller.ex",
    "content": "defmodule MiphaWeb.NotificationController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Notifications\n\n  plug MiphaWeb.Plug.RequireUser\n\n  def index(conn, params) do\n    result =\n      conn\n      |> current_user()\n      |> Notifications.Queries.cond_user_notifications()\n      |> Turbo.Ecto.turbo(params)\n\n    render(conn, :index,\n      notifications: result.datas,\n      paginate: result.paginate\n    )\n  end\n\n  def make_read(conn, _params) do\n    conn\n    |> current_user()\n    |> Notifications.mark_read_notification()\n\n    conn\n    |> put_flash(:info, gettext(\"Marked all notifications readed.\"))\n    |> redirect(to: notification_path(conn, :index))\n  end\n\n  def clean(conn, _params) do\n    conn\n    |> current_user()\n    |> Notifications.clean_notification()\n\n    conn\n    |> put_flash(:info, gettext(\"Cleaned notification successfully\"))\n    |> redirect(to: notification_path(conn, :index))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/page_controller.ex",
    "content": "defmodule MiphaWeb.PageController do\n  use MiphaWeb, :controller\n\n  alias Mipha.Markdown\n\n  def index(conn, _params) do\n    render(conn, :index)\n  end\n\n  def markdown(conn, _) do\n    markdown_ex = Markdown.example()\n    render(conn, :markdown, markdown_ex: markdown_ex)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/reply_controller.ex",
    "content": "defmodule MiphaWeb.ReplyController do\n  use MiphaWeb, :controller\n\n  import MiphaWeb.Endpoint, only: [broadcast!: 3]\n  alias Mipha.{Stars, Replies, Topics}\n\n  plug MiphaWeb.Plug.RequireUser when action in ~w(create edit update delete star unstar)a\n\n  def action(conn, _) do\n    topic = Topics.get_topic!(conn.params[\"topic_id\"])\n    apply(__MODULE__, action_name(conn), [conn, conn.params, topic])\n  end\n\n  def create(conn, %{\"reply\" => reply_params}, topic) do\n    case Replies.insert_reply(current_user(conn), reply_params) do\n      {:ok, %{reply: reply}} ->\n        # broadcast topic:id\n        broadcast!(\"topic:#{topic.id}\", \"topic:#{topic.id}:new_reply\", %{reply_id: reply.id})\n\n        conn\n        |> put_flash(:success, \"Reply created successfully.\")\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, _} ->\n        conn\n        |> put_flash(:success, \"Reply created error.\")\n        |> redirect(to: topic_path(conn, :show, topic))\n    end\n  end\n\n  def edit(conn, %{\"id\" => id}, topic) do\n    reply = Replies.get_reply!(id)\n    changeset = Replies.change_reply(reply)\n\n    render(conn, :edit,\n      topic: topic,\n      changeset: changeset,\n      reply: reply,\n      topic: topic\n    )\n  end\n\n  def update(conn, %{\"id\" => id, \"reply\" => reply_params}, topic) do\n    reply = Replies.get_reply!(id)\n\n    case Replies.update_reply(reply, reply_params) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:success, gettext(\"Reply updated successfully.\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, _} ->\n        conn\n        |> put_flash(:success, gettext(\"Reply updated error.\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n    end\n  end\n\n  def delete(conn, %{\"id\" => id}, topic) do\n    reply = Replies.get_reply!(id)\n    {:ok, _reply} = Replies.delete_reply(reply)\n\n    conn\n    |> put_flash(:info, gettext(\"Reply deleted successfully.\"))\n    |> redirect(to: topic_path(conn, :show, topic))\n  end\n\n  def star(conn, %{\"reply_id\" => reply_id}, topic) do\n    reply = Replies.get_reply!(reply_id)\n\n    attrs = %{\n      user_id: current_user(conn).id,\n      reply_id: reply.id\n    }\n\n    case Stars.insert_star(attrs) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:info, gettext(\"Star successfully\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, _} ->\n        conn\n        |> put_flash(:danger, gettext(\"Star failed\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n    end\n  end\n\n  def unstar(conn, %{\"reply_id\" => reply_id}, topic) do\n    reply = Replies.get_reply!(reply_id)\n\n    attrs = [\n      user_id: current_user(conn).id,\n      reply_id: reply.id\n    ]\n\n    case Stars.delete_star(attrs) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:info, gettext(\"Unstar successfully\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, _} ->\n        conn\n        |> put_flash(:danger, gettext(\"Unstar failed\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/search_controller.ex",
    "content": "defmodule MiphaWeb.SearchController do\n  use MiphaWeb, :controller\n\n  alias Mipha.{Accounts, Qiniu}\n\n  def index(conn, _) do\n    render(conn, :index)\n  end\n\n  @doc \"\"\"\n  Filters the mention users.\n  \"\"\"\n  def users(conn, params) do\n    users =\n      conn\n      |> current_user()\n      |> Accounts.search_mention_user(params[\"q\"])\n      |> Enum.map(&%{login: &1.username, avatar_url: Qiniu.q_url(&1.avatar)})\n\n    json(conn, users)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/session_controller.ex",
    "content": "defmodule MiphaWeb.SessionController do\n  use MiphaWeb, :controller\n\n  alias Mipha.{Accounts, Mailer, Token}\n  alias MiphaWeb.Email\n\n  plug :authorized_user\n\n  def new(conn, _params) do\n    changeset = Accounts.user_register_changeset()\n    render(conn, :new, changeset: changeset)\n  end\n\n  def excaptcha(conn, _) do\n    {:ok, text, img_binary} = Captcha.get(50_000)\n\n    conn\n    |> put_session(:excaptcha, text)\n    |> put_resp_header(\"Cache-Control\", \"no-cache, no-store, max-age=0, must-revalidate\")\n    |> put_resp_header(\"Pragma\", \"no-cache\")\n    |> put_resp_content_type(\"image/gif\")\n    |> send_resp(200, img_binary)\n  end\n\n  def create(conn, %{\"user\" => user_params, \"_excaptcha\" => captcha}) do\n    # Validation captcha.\n    is_true_captcha =\n      conn\n      |> get_session(:excaptcha)\n      |> String.equivalent?(captcha)\n\n    unless is_true_captcha do\n      changeset = Accounts.user_register_changeset(user_params)\n\n      conn\n      |> put_flash(:danger, gettext(\"captcha error, pls input again.\"))\n      |> render(:new, changeset: changeset)\n    end\n\n    case Accounts.register_user(user_params) do\n      {:ok, user} ->\n        sent_welcome_email(user)\n\n        conn\n        |> ok_login(user)\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        conn\n        |> put_flash(:danger, gettext(\"Register failed. pls try again.\"))\n        |> render(:new, changeset: changeset)\n    end\n  end\n\n  defp sent_welcome_email(user) do\n    user\n    |> Token.generate_token()\n    |> Email.welcome(user)\n    |> Mailer.deliver_later()\n  end\n\n  defp ok_login(conn, user) do\n    conn\n    |> put_flash(:info, gettext(\"Registered successfully\"))\n    |> put_session(:current_user, user.id)\n    |> redirect(to: \"/\")\n  end\n\n  # if user login,can't reach this controller.\n  defp authorized_user(conn, _) do\n    if current_user(conn) do\n      conn\n      |> put_flash(:danger, \"You has logged in\")\n      |> redirect(to: \"/\")\n    else\n      conn\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/setting_controller.ex",
    "content": "defmodule MiphaWeb.SettingController do\n  use MiphaWeb, :controller\n\n  alias Mipha.{Accounts, Qiniu}\n\n  plug MiphaWeb.Plug.RequireUser\n\n  @intercepted_action ~w(show account password profile reward)a\n\n  def action(conn, _) do\n    if Enum.member?(@intercepted_action, action_name(conn)) do\n      changeset = Accounts.change_user(current_user(conn))\n      render(conn, action_name(conn), changeset: changeset)\n    else\n      apply(__MODULE__, action_name(conn), [conn, conn.params])\n    end\n  end\n\n  def update(conn, %{\"user\" => user_params}) do\n    case user_params[\"by\"] do\n      \"show\" -> update_account(conn, user_params)\n      \"profile\" -> update_profile(conn, user_params)\n      \"password\" -> update_password(conn, user_params)\n      \"reward\" -> update_reward(conn, user_params)\n    end\n  end\n\n  defp update_account(conn, user_params) do\n    # FIXME need more better method.\n    attrs =\n      user_params\n      |> Map.pop(\"avatar\")\n      |> build_attrs(\"avatar\")\n\n    case Accounts.update_user(current_user(conn), attrs) do\n      {:ok, user} ->\n        conn\n        |> put_flash(:info, gettext(\"Profile updated successfully\"))\n        |> render(:show, changeset: Accounts.change_user(user))\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        conn\n        |> put_flash(:danger, gettext(\"Profile updated failed\"))\n        |> render(:show, changeset: changeset)\n    end\n  end\n\n  defp update_profile(conn, user_params) do\n    case Accounts.update_user(current_user(conn), user_params) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:info, gettext(\"User updated successfully.\"))\n        |> redirect(to: setting_profile_path(conn, :profile))\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        conn\n        |> put_flash(:danger, gettext(\"User updated failed.\"))\n        |> render(:password, changeset: changeset)\n    end\n  end\n\n  defp update_password(conn, user_params) do\n    case Accounts.update_user_password(current_user(conn), user_params) do\n      {:ok, _} ->\n        conn\n        |> configure_session(drop: true)\n        |> put_flash(:info, gettext(\"User updated password successfully.\"))\n        |> redirect(to: \"/\")\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        conn\n        |> put_flash(:danger, gettext(\"Invalid password\"))\n        |> render(:password, changeset: changeset)\n\n      {:error, reason} ->\n        changeset = Accounts.user_update_password_changeset()\n\n        conn\n        |> put_flash(:danger, reason)\n        |> render(:password, changeset: changeset)\n    end\n  end\n\n  defp update_reward(conn, user_params) do\n    attrs =\n      user_params\n      |> Map.pop(\"wechat\")\n      |> build_attrs(\"wechat\")\n      |> Map.pop(\"alipay\")\n      |> build_attrs(\"alipay\")\n\n    case Accounts.update_user(current_user(conn), attrs) do\n      {:ok, user} ->\n        changeset = Accounts.change_user(user)\n\n        conn\n        |> put_flash(:info, gettext(\"User updated successfully.\"))\n        |> render(:show, changeset: changeset)\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        conn\n        |> put_flash(:danger, gettext(\"User updated failed.\"))\n        |> render(:show, changeset: changeset)\n    end\n  end\n\n  defp build_attrs({nil, params}, _), do: params\n\n  defp build_attrs({%Plug.Upload{} = avatar, params}, field) do\n    # FIXME need set global callback\n    with %HTTPoison.Response{status_code: 200, body: body} <- Qiniu.upload(avatar.path) do\n      Map.merge(params, %{field => body[\"key\"]})\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/team_controller.ex",
    "content": "defmodule MiphaWeb.TeamController do\n  use MiphaWeb, :controller\n\n  alias Mipha.{Accounts, Topics}\n  alias Topics.Queries\n\n  def index(conn, _params) do\n    render(conn, :index)\n  end\n\n  def show(conn, %{\"id\" => id} = params) do\n    team = Accounts.get_team!(id)\n    team_user_ids = Enum.map(team.users, & &1.id)\n\n    result =\n      [user_ids: team_user_ids]\n      |> Queries.cond_topics()\n      |> Turbo.Ecto.turbo(params)\n\n    render(conn, :show,\n      team: team,\n      topics: result.datas,\n      paginate: result.paginate\n    )\n  end\n\n  def people(conn, %{\"id\" => id}) do\n    team = Accounts.get_team!(id)\n    render(conn, :people, team: team)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/topic_controller.ex",
    "content": "defmodule MiphaWeb.TopicController do\n  use MiphaWeb, :controller\n\n  alias Mipha.{Topics, Stars, Collections, Markdown}\n  alias Mipha.Topics.Queries\n\n  plug MiphaWeb.Plug.RequireUser when action in ~w(\n    new create edit update\n    star unstar collection uncollection\n    unsuggest suggest close open excellent normal\n  )a\n\n  @intercepted_action ~w(index no_reply popular featured educational)a\n\n  def action(conn, _) do\n    conn\n    |> action_name\n    |> do_fragment(conn)\n  end\n\n  defp do_fragment(action_name, conn) when action_name in @intercepted_action do\n    opts =\n      if conn.params[\"node_id\"] do\n        [node: Topics.get_node!(conn.params[\"node_id\"])]\n      else\n        [type: action_name(conn)]\n      end\n\n    parent_nodes = Topics.list_parent_nodes()\n\n    result =\n      opts\n      |> Queries.cond_topics()\n      |> Turbo.Ecto.turbo(conn.params)\n\n    render(conn, action_name(conn),\n      asset: \"topics\",\n      topics: result.datas,\n      paginate: result.paginate,\n      parent_nodes: parent_nodes\n    )\n  end\n\n  defp do_fragment(:suggest, conn) do\n    topic = Topics.get_topic!(conn.params[\"id\"])\n    attrs = %{\"suggested_at\" => Timex.now()}\n    flash = gettext(\"Pin\")\n\n    do_update(conn, topic, attrs, flash)\n  end\n\n  defp do_fragment(:unsuggest, conn) do\n    topic = Topics.get_topic!(conn.params[\"id\"])\n    attrs = %{\"suggested_at\" => nil}\n    flash = gettext(\"Unpin\")\n\n    do_update(conn, topic, attrs, flash)\n  end\n\n  defp do_fragment(:close, conn) do\n    topic = Topics.get_topic!(conn.params[\"id\"])\n    attrs = %{\"closed_at\" => Timex.now()}\n    flash = gettext(\"Closed\")\n\n    do_update(conn, topic, attrs, flash)\n  end\n\n  defp do_fragment(:open, conn) do\n    topic = Topics.get_topic!(conn.params[\"id\"])\n    attrs = %{\"closed_at\" => nil}\n    flash = gettext(\"Opened\")\n\n    do_update(conn, topic, attrs, flash)\n  end\n\n  defp do_fragment(:excellent, conn) do\n    topic = Topics.get_topic!(conn.params[\"id\"])\n    attrs = %{\"type\" => \"featured\"}\n    flash = gettext(\"Featured topic\")\n\n    do_update(conn, topic, attrs, flash)\n  end\n\n  defp do_fragment(:normal, conn) do\n    topic = Topics.get_topic!(conn.params[\"id\"])\n    attrs = %{\"type\" => \"normal\"}\n    flash = gettext(\"Normal topic\")\n\n    do_update(conn, topic, attrs, flash)\n  end\n\n  defp do_fragment(_, conn) do\n    apply(__MODULE__, action_name(conn), [conn, conn.params])\n  end\n\n  # update topic\n  defp do_update(conn, topic, attrs, flash) do\n    {:ok, topic} = Topics.update_topic(topic, attrs)\n\n    conn\n    |> put_flash(:success, flash)\n    |> redirect(to: topic_path(conn, :show, topic))\n  end\n\n  def jobs(conn, params) do\n    parent_nodes = Topics.list_parent_nodes()\n    result = Turbo.Ecto.turbo(Queries.job_topics(), params)\n\n    render(conn, :jobs,\n      parent_nodes: parent_nodes,\n      topics: result.datas,\n      paginate: result.paginate\n    )\n  end\n\n  def new(conn, _params) do\n    changeset = Topics.change_topic()\n    parent_nodes = Topics.list_parent_nodes()\n\n    render(conn, :new,\n      changeset: changeset,\n      parent_nodes: parent_nodes\n    )\n  end\n\n  def edit(conn, %{\"id\" => id}) do\n    topic = Topics.get_topic!(id)\n    changeset = Topics.change_topic(topic)\n    parent_nodes = Topics.list_parent_nodes()\n\n    render(conn, :edit,\n      topic: topic,\n      changeset: changeset,\n      parent_nodes: parent_nodes\n    )\n  end\n\n  def create(conn, %{\"topic\" => topic_params}) do\n    case Topics.insert_topic(current_user(conn), topic_params) do\n      {:ok, %{topic: topic}} ->\n        conn\n        |> put_flash(:success, gettext(\"Topic created successfully.\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, :topic, %Ecto.Changeset{} = changeset, _} ->\n        parent_nodes = Topics.list_parent_nodes()\n\n        conn\n        |> put_flash(:danger, gettext(\"Create topic failed, pls select node_id.\"))\n        |> render(:new, changeset: changeset, parent_nodes: parent_nodes)\n    end\n  end\n\n  def update(conn, %{\"id\" => id, \"topic\" => topic_params}) do\n    topic = Topics.get_topic!(id)\n\n    case Topics.update_topic(topic, topic_params) do\n      {:ok, topic} ->\n        conn\n        |> put_flash(:success, gettext(\"Topic updated successfully.\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        parent_nodes = Topics.list_parent_nodes()\n        render(conn, :edit, topic: topic, changeset: changeset, parent_nodes: parent_nodes)\n    end\n  end\n\n  def show(conn, %{\"id\" => id}) do\n    topic = Topics.get_topic!(id)\n\n    # Same session, same visit counter.\n    if is_nil(get_session(conn, \"visited_topic_#{topic.id}\")) do\n      # increment topic visit count.\n      Topics.topic_visit_counter(topic)\n\n      conn\n      |> put_session(\"visited_topic_#{topic.id}\", topic.id)\n      |> render(:show, topic: topic)\n    else\n      render(conn, :show, topic: topic)\n    end\n  end\n\n  def delete(conn, %{\"id\" => id}) do\n    topic = Topics.get_topic!(id)\n    {:ok, _topic} = Topics.delete_topic(topic)\n\n    conn\n    |> put_flash(:info, gettext(\"Topic deleted successfully.\"))\n    |> redirect(to: topic_path(conn, :index))\n  end\n\n  def preview(conn, %{\"body\" => body}) do\n    json(conn, %{body: Markdown.render(body)})\n  end\n\n  def star(conn, %{\"id\" => id}) do\n    topic = Topics.get_topic!(id)\n\n    attrs = %{\n      user_id: current_user(conn).id,\n      topic_id: topic.id\n    }\n\n    case Stars.insert_star(attrs) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:info, gettext(\"Star successfully\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, _} ->\n        conn\n        |> put_flash(:danger, gettext(\"Star failed\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n    end\n  end\n\n  def unstar(conn, %{\"id\" => id}) do\n    topic = Topics.get_topic!(id)\n\n    attrs = [\n      user_id: current_user(conn).id,\n      topic_id: topic.id\n    ]\n\n    case Stars.delete_star(attrs) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:info, gettext(\"Unstar successfully\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, _} ->\n        conn\n        |> put_flash(:danger, gettext(\"Unstar failed\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n    end\n  end\n\n  def collection(conn, %{\"id\" => id}) do\n    topic = Topics.get_topic!(id)\n\n    attrs = %{\n      user_id: current_user(conn).id,\n      topic_id: topic.id\n    }\n\n    case Collections.insert_collection(attrs) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:info, gettext(\"Collection successfully\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, _} ->\n        conn\n        |> put_flash(:danger, gettext(\"Collection failed\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n    end\n  end\n\n  def uncollection(conn, %{\"id\" => id}) do\n    topic = Topics.get_topic!(id)\n\n    attrs = [\n      user_id: current_user(conn).id,\n      topic_id: topic.id\n    ]\n\n    case Collections.delete_collection(attrs) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:info, gettext(\"Uncollection successfully\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n\n      {:error, _} ->\n        conn\n        |> put_flash(:danger, gettext(\"Uncollection failed\"))\n        |> redirect(to: topic_path(conn, :show, topic))\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/controllers/user_controller.ex",
    "content": "defmodule MiphaWeb.UserController do\n  use MiphaWeb, :controller\n\n  alias Mipha.{Mailer, Accounts, Topics, Replies, Follows, Collections, Token}\n  alias MiphaWeb.Email\n\n  plug MiphaWeb.Plug.RequireUser when action in [:follow, :unfollow]\n\n  def action(conn, _) do\n    if conn.params[\"name\"] do\n      case Accounts.get_user_by_username(conn.params[\"name\"]) do\n        nil ->\n          conn\n          |> put_flash(:danger, gettext(\"User not exist.\"))\n          |> redirect(to: \"/\")\n\n        user ->\n          apply(__MODULE__, action_name(conn), [conn, conn.params, user])\n      end\n    else\n      apply(__MODULE__, action_name(conn), [conn, conn.params])\n    end\n  end\n\n  def index(conn, params) do\n    result = Accounts.Queries.list_users() |> Turbo.Ecto.turbo(params)\n    user_count = Accounts.get_user_count()\n\n    render(conn, :index,\n      users: result.datas,\n      user_count: user_count\n    )\n  end\n\n  def show(conn, _params, user) do\n    topics = Topics.recent_topics(user)\n    replies = Replies.recent_replies(user)\n\n    render(conn, :show,\n      user: user,\n      topics: topics,\n      replies: replies\n    )\n  end\n\n  def topics(conn, params, user) do\n    result = Topics.Queries.cond_topics(user: user) |> Turbo.Ecto.turbo(params)\n\n    render(conn, :topics,\n      user: user,\n      paginate: result.paginate,\n      topics: result.datas\n    )\n  end\n\n  def replies(conn, params, user) do\n    result = Replies.Queries.cond_replies(user: user) |> Turbo.Ecto.turbo(params)\n\n    render(conn, :replies,\n      user: user,\n      paginate: result.paginate,\n      replies: result.datas\n    )\n  end\n\n  def following(conn, params, user) do\n    result = Follows.Queries.cond_follows(follower: user) |> Turbo.Ecto.turbo(params)\n\n    render(conn, :following,\n      user: user,\n      paginate: result.paginate,\n      following: result.datas\n    )\n  end\n\n  def followers(conn, params, user) do\n    result = Follows.Queries.cond_follows(user: user) |> Turbo.Ecto.turbo(params)\n\n    render(conn, :followers,\n      user: user,\n      paginate: result.paginate,\n      followers: result.datas\n    )\n  end\n\n  def collections(conn, params, user) do\n    result = Collections.Queries.cond_collections(user: user) |> Turbo.Ecto.turbo(params)\n\n    render(conn, :collections,\n      user: user,\n      paginate: result.paginate,\n      collections: result.datas\n    )\n  end\n\n  def follow(conn, _params, user) do\n    case Follows.follow_user(follower: current_user(conn), user: user) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:info, gettext(\"Follow successfully.\"))\n        |> redirect(to: user_path(conn, :show, user.username))\n\n      {:error, %Ecto.Changeset{}} ->\n        conn\n        |> put_flash(:danger, gettext(\"Follow failed.\"))\n        |> redirect(to: user_path(conn, :show, user.username))\n\n      {:error, reason} ->\n        conn\n        |> put_flash(:danger, reason)\n        |> redirect(to: user_path(conn, :show, user.username))\n    end\n  end\n\n  def unfollow(conn, _params, user) do\n    case Follows.unfollow_user(follower: current_user(conn), user: user) do\n      {:ok, _} ->\n        conn\n        |> put_flash(:info, gettext(\"Unfollow successfully.\"))\n        |> redirect(to: user_path(conn, :show, user.username))\n\n      {:error, %Ecto.Changeset{}} ->\n        conn\n        |> put_flash(:danger, gettext(\"Unfollow failed.\"))\n        |> redirect(to: user_path(conn, :show, user.username))\n\n      {:error, reason} ->\n        conn\n        |> put_flash(:danger, reason)\n        |> redirect(to: user_path(conn, :show, user.username))\n    end\n  end\n\n  def sent_forgot_password_email(conn, %{\"user\" => user_params}) do\n    with {:ok, email} <- parse(user_params[\"email\"]),\n         {:ok, user} <- parse(Accounts.get_user_by_email(email)) do\n      # Send email\n      user\n      |> Token.generate_token()\n      |> Email.forgot_password(user)\n      |> Mailer.deliver_later()\n\n      conn\n      |> put_flash(:success, gettext(\"You will receive an email to reset your password.\"))\n      |> redirect(to: \"/\")\n    else\n      _ ->\n        conn\n        |> put_flash(:danger, gettext(\"The email is invalid.\"))\n        |> redirect(to: \"/forgot_password\")\n    end\n  end\n\n  def forgot_password(conn, _) do\n    render(conn, :forgot_password)\n  end\n\n  def reset_password(conn, %{\"token\" => token}) do\n    with {:ok, user_id} <- Token.verify_token(token) do\n      user = Accounts.get_user!(user_id)\n      changeset = Accounts.change_user_reset_password(user)\n\n      render(conn, :reset_password,\n        user: user,\n        token: token,\n        changeset: changeset\n      )\n    else\n      _ -> render(conn, :invalid_token)\n    end\n  end\n\n  def reset_password(conn, _) do\n    conn\n    |> put_flash(:danger, gettext(\"The verification link is invalid.\"))\n    |> redirect(to: \"/\")\n  end\n\n  def update_password(conn, %{\"user\" => user_params}) do\n    with {:ok, token} <- parse(user_params[\"reset_password_token\"]),\n         {:ok, user_id} <- Token.verify_token(token) do\n      user = Accounts.get_user!(user_id)\n\n      case Accounts.update_reset_password(user, user_params) do\n        {:ok, _} ->\n          conn\n          |> put_flash(:success, gettext(\"Reset password successfully.\"))\n          |> redirect(to: \"/login\")\n\n        {:error, %Ecto.Changeset{} = changeset} ->\n          render(conn, :reset_password,\n            changeset: changeset,\n            user: user,\n            token: token\n          )\n      end\n    else\n      _ -> render(conn, :invalid_token)\n    end\n  end\n\n  # def verify_email(conn, %{\"token\" => token}) do\n  #   with {:ok, user_id} <- Token.verify_token(token),\n  #        %User{email_verified_at: nil} = user <- Accounts.get_user!(user_id)\n  #   do\n  #     Accounts.mark_as_verified(user)\n  #     render conn, :verified\n  #   else\n  #     _ -> render conn, :invalid_token\n  #   end\n  # end\n\n  # def verify_email(conn, _) do\n  #   conn\n  #   |> put_flash(:danger, \"The verification link is invalid.\")\n  #   |> redirect(to: \"/\")\n  # end\n\n  defp parse(nil), do: {:error, \"nil\"}\n  defp parse(valid), do: {:ok, valid}\nend\n"
  },
  {
    "path": "lib/mipha_web/email.ex",
    "content": "defmodule MiphaWeb.Email do\n  @moduledoc \"\"\"\n  发送邮件集。\n  \"\"\"\n\n  use Bamboo.Phoenix, view: MiphaWeb.EmailView\n\n  @from \"andzven@gmail.com\"\n\n  @doc \"\"\"\n  重置密码邮件。\n  \"\"\"\n  def forgot_password(token, user) do\n    subject = \"重置密码信息\"\n\n    normal_email()\n    |> from(@from)\n    |> to(user.email)\n    |> subject(subject)\n    |> assign(:user, user)\n    |> assign(:token, token)\n    |> render(\"forgot_password.html\")\n  end\n\n  @doc \"\"\"\n  欢迎邮件。\n  \"\"\"\n  def welcome(token, user) do\n    subject = \"欢迎加入 Elixir Mipha 社区\"\n\n    normal_email()\n    |> from(@from)\n    |> to(user.email)\n    |> subject(subject)\n    |> assign(:user, user)\n    |> assign(:token, token)\n    |> render(\"welcome.html\")\n  end\n\n  @doc \"\"\"\n  验证用户登录邮箱\n  \"\"\"\n  def verify_email(token, user) do\n    subject = \"激活邮箱\"\n\n    normal_email()\n    |> from(@from)\n    |> to(user.email)\n    |> subject(subject)\n    |> assign(:user, user)\n    |> assign(:token, token)\n    |> render(\"verify_email.html\")\n  end\n\n  defp normal_email do\n    new_email()\n    |> put_html_layout({MiphaWeb.LayoutView, \"email.html\"})\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/endpoint.ex",
    "content": "defmodule MiphaWeb.Endpoint do\n  use Phoenix.Endpoint, otp_app: :mipha\n\n  socket \"/socket\", MiphaWeb.UserSocket,\n    # or list of options\n    websocket: true,\n    longpoll: false\n\n  # Serve at \"/\" the static files from \"priv/static\" directory.\n  #\n  # You should set gzip to true if you are running phoenix.digest\n  # when deploying your static files in production.\n  plug Plug.Static,\n    at: \"/\",\n    from: :mipha,\n    gzip: false,\n    only: ~w(css fonts images js favicon.ico robots.txt)\n\n  # Code reloading can be explicitly enabled under the\n  # :code_reloader configuration of your endpoint.\n  if code_reloading? do\n    socket(\"/phoenix/live_reload/socket\", Phoenix.LiveReloader.Socket)\n    plug Phoenix.LiveReloader\n    plug Phoenix.CodeReloader\n  end\n\n  plug Plug.Logger\n\n  plug Plug.Parsers,\n    parsers: [:urlencoded, :multipart, :json],\n    pass: [\"*/*\"],\n    json_decoder: Jason\n\n  plug Plug.MethodOverride\n  plug Plug.Head\n\n  # The session will be stored in the cookie and signed,\n  # this means its contents can be read but not tampered with.\n  # Set :encryption_salt if you would also like to encrypt it.\n  plug Plug.Session,\n    store: :cookie,\n    key: \"_mipha_key\",\n    signing_salt: \"7l+jtdHA\"\n\n  # Safe，limited。\n  plug RemoteIp\n  plug MiphaWeb.Plug.Attack\n\n  plug MiphaWeb.Router\n\n  # Callback invoked for dynamically configuring the endpoint.\n  # It receives the endpoint configuration and checks if\n  # configuration should be loaded from the system environment.\n  def init(_key, config) do\n    if config[:load_from_system_env] do\n      port = System.get_env(\"PORT\") || raise \"expected the PORT environment variable to be set\"\n      {:ok, Keyword.put(config, :http, [:inet6, port: port])}\n    else\n      {:ok, config}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/gettext.ex",
    "content": "defmodule MiphaWeb.Gettext do\n  @moduledoc \"\"\"\n  A module providing Internationalization with a gettext-based API.\n\n  By using [Gettext](https://hexdocs.pm/gettext),\n  your module gains a set of macros for translations, for example:\n\n      import MiphaWeb.Gettext\n\n      # Simple translation\n      gettext \"Here is the string to translate\"\n\n      # Plural translation\n      ngettext \"Here is the string to translate\",\n               \"Here are the strings to translate\",\n               3\n\n      # Domain-based translation\n      dgettext \"errors\", \"Here is the error message to translate\"\n\n  See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.\n  \"\"\"\n  use Gettext, otp_app: :mipha\nend\n"
  },
  {
    "path": "lib/mipha_web/plugs/attack.ex",
    "content": "defmodule MiphaWeb.Plug.Attack do\n  @moduledoc \"\"\"\n  attack\n  \"\"\"\n\n  import Plug.Conn\n  use PlugAttack\n\n  rule(\"throttle by ip every 2 minutes only allow register 10 person\", conn) do\n    if conn.method == \"POST\" and conn.path_info == [\"join\"] do\n      throttle(conn.remote_ip,\n        period: 120_000,\n        limit: 10,\n        storage: {PlugAttack.Storage.Ets, Mipha.PlugAttack.Storage}\n      )\n    end\n  end\n\n  rule(\"throttle by ip every 1 hours only allow create 20 topic\", conn) do\n    if conn.method == \"POST\" and conn.path_info == [\"topics\"] do\n      throttle(conn.remote_ip,\n        period: 3_600_000,\n        limit: 20,\n        storage: {PlugAttack.Storage.Ets, Mipha.PlugAttack.Storage}\n      )\n    end\n  end\n\n  rule(\"throttle by ip every 2 minutes only allow create 20 reply\", conn) do\n    if conn.method == \"POST\" and conn.path_info == [\"topics\"] do\n      throttle(conn.remote_ip,\n        period: 120_000,\n        limit: 20,\n        storage: {PlugAttack.Storage.Ets, Mipha.PlugAttack.Storage}\n      )\n    end\n  end\n\n  def allow_action(conn, {:throttle, data}, opts) do\n    conn\n    |> add_throttling_headers(data)\n    |> allow_action(true, opts)\n  end\n\n  def allow_action(conn, _data, _opts) do\n    conn\n  end\n\n  def block_action(conn, {:throttle, data}, opts) do\n    conn\n    |> add_throttling_headers(data)\n    |> block_action(false, opts)\n  end\n\n  def block_action(conn, _data, _opts) do\n    conn\n    |> send_resp(:forbidden, \"Forbidden \\n\")\n    |> halt()\n  end\n\n  defp add_throttling_headers(conn, data) do\n    # in seconds\n    reset = div(data[:expires_at], 1_000)\n\n    conn\n    |> put_resp_header(\"x-ratelimit-limit\", to_string(data[:limit]))\n    |> put_resp_header(\"x-ratelimit-remaining\", to_string(data[:remaining]))\n    |> put_resp_header(\"x-ratelimit-reset\", to_string(reset))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/plugs/current_user.ex",
    "content": "defmodule MiphaWeb.Plug.CurrentUser do\n  @moduledoc \"\"\"\n  A `Plug` to assign `:current_user` && `:user_token` based on the session\n  \"\"\"\n\n  import Plug.Conn\n  import MiphaWeb.Session, only: [current_user: 1, user_token: 1]\n\n  def init(opts), do: opts\n\n  def call(conn, _opts) do\n    conn\n    |> assign(:current_user, current_user(conn))\n    |> assign(:user_token, user_token(conn))\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/plugs/locale.ex",
    "content": "defmodule MiphaWeb.Plug.Locale do\n  @moduledoc \"\"\"\n  Locale, support: 简体中文 and English\n  \"\"\"\n\n  import Plug.Conn\n\n  def init(_opts), do: nil\n\n  def call(conn, _opts) do\n    case conn.params[\"locale\"] || get_session(conn, :locale) do\n      nil ->\n        conn\n\n      locale ->\n        Gettext.put_locale(MiphaWeb.Gettext, locale)\n        conn |> put_session(:locale, locale)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/plugs/require_admin.ex",
    "content": "defmodule MiphaWeb.Plug.RequireAdmin do\n  @moduledoc false\n\n  import Plug.Conn\n  import Phoenix.Controller\n\n  alias MiphaWeb.Router.Helpers\n\n  def init(opts), do: opts\n\n  def call(conn, _opts) do\n    user = conn.assigns[:current_user]\n\n    if user && user.is_admin do\n      conn\n    else\n      conn\n      |> put_flash(:danger, \"Admin only\")\n      |> redirect(to: Helpers.topic_path(conn, :index))\n      |> halt()\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/plugs/require_user.ex",
    "content": "defmodule MiphaWeb.Plug.RequireUser do\n  @moduledoc \"\"\"\n  A `Plug` to redirect to `pages/index` if there is no current user.\n  \"\"\"\n\n  import Plug.Conn\n  import Phoenix.Controller, only: [put_flash: 3, redirect: 2]\n  import MiphaWeb.Router.Helpers\n  import MiphaWeb.Session, only: [user_logged_in?: 1]\n\n  def init(opts), do: opts\n\n  def call(conn, _opts) do\n    if user_logged_in?(conn) do\n      conn\n    else\n      conn\n      |> put_flash(:danger, \"You must login in.\")\n      |> redirect(to: auth_path(conn, :login))\n      |> halt()\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/router.ex",
    "content": "defmodule MiphaWeb.Router do\n  use MiphaWeb, :router\n  use Plug.ErrorHandler\n  use Sentry.Plug\n\n  pipeline :browser do\n    plug Ueberauth\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_flash\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n    plug MiphaWeb.Plug.CurrentUser\n    plug MiphaWeb.Plug.Locale\n  end\n\n  pipeline :api do\n    plug :accepts, [\"json\"]\n  end\n\n  pipeline :admin do\n    plug :put_layout, {MiphaWeb.LayoutView, :admin}\n    plug MiphaWeb.Plug.RequireAdmin\n  end\n\n  scope \"/auth\", MiphaWeb do\n    pipe_through(:browser)\n\n    get \"/:provider\", AuthController, :request\n    get \"/:provider/callback\", AuthController, :callback\n    post \"/:provider/callback\", AuthController, :callback\n  end\n\n  scope \"/\", MiphaWeb do\n    # Use the default browser stack\n    pipe_through(:browser)\n\n    get \"/\", TopicController, :index\n    get \"/search\", SearchController, :index\n    get \"/search/users\", SearchController, :users\n    get \"/markdown\", PageController, :markdown\n    get(\"/join\", SessionController, :new, as: :join)\n    post(\"/join\", SessionController, :create, as: :join)\n    get \"/excaptcha\", SessionController, :excaptcha\n    get \"/login\", AuthController, :login\n    get(\"/logout\", AuthController, :delete, as: :logout)\n\n    get \"/u/:name\", UserController, :show\n    get(\"/u/:name/topics\", UserController, :topics, as: :user_topics)\n    get(\"/u/:name/replies\", UserController, :replies, as: :user_replies)\n    get(\"/u/:name/stars\", UserController, :stars, as: :user_stars)\n    get(\"/u/:name/collections\", UserController, :collections, as: :user_collections)\n    post(\"/u/:name/follow\", UserController, :follow, as: :user_follow)\n    post(\"/u/:name/unfollow\", UserController, :unfollow, as: :user_unfollow)\n    get(\"/u/:name/followers\", UserController, :followers, as: :user_followers)\n    get(\"/u/:name/following\", UserController, :following, as: :user_following)\n    get(\"/u/:name/reward\", UserController, :reward, as: :user_reward)\n\n    get \"/users\", UserController, :index\n    get \"/forgot_password\", UserController, :forgot_password\n    get \"/reset_password\", UserController, :reset_password\n    # get \"/verify\", UserController, :verify_email\n    put \"/users/update_password\", UserController, :update_password\n    post \"/users/sent_forgot_password_email\", UserController, :sent_forgot_password_email\n    post \"/users/sent_verify_email\", UserController, :sent_verify_email\n    resources(\"/teams\", TeamController)\n    get \"/teams/:id/people\", TeamController, :people\n\n    # topic\n    get \"/jobs\", TopicController, :jobs\n    get \"/topics/no_reply\", TopicController, :no_reply\n    get \"/topics/popular\", TopicController, :popular\n    get \"/topics/featured\", TopicController, :featured\n    get \"/topics/educational\", TopicController, :educational\n\n    post \"/topics/:id/star\", TopicController, :star\n    post \"/topics/:id/unstar\", TopicController, :unstar\n    post \"/topics/:id/collection\", TopicController, :collection\n    post \"/topics/:id/uncollection\", TopicController, :uncollection\n    post \"/topics/:id/suggest\", TopicController, :suggest\n    post \"/topics/:id/unsuggest\", TopicController, :unsuggest\n    post \"/topics/:id/close\", TopicController, :close\n    post \"/topics/:id/open\", TopicController, :open\n    post \"/topics/:id/excellent\", TopicController, :excellent\n    post \"/topics/:id/normal\", TopicController, :normal\n\n    resources \"/topics\", TopicController do\n      resources \"/replies\", ReplyController do\n        post \"/star\", ReplyController, :star\n        post \"/unstar\", ReplyController, :unstar\n      end\n    end\n\n    resources(\"/notifications\", NotificationController, only: ~w(index)a)\n    post \"/notifications/make_read\", NotificationController, :make_read\n    delete \"/notifications/clean\", NotificationController, :clean\n\n    resources(\"/locations\", LocationController, only: ~w(index show)a)\n    resources(\"/companies\", CompanyController, only: ~w(index show)a)\n\n    # User profile\n    resources \"/setting\", SettingController, only: ~w(show update)a, singleton: true do\n      get(\"/account\", SettingController, :account, as: :account)\n      get(\"/password\", SettingController, :password, as: :password)\n      get(\"/profile\", SettingController, :profile, as: :profile)\n      get(\"/reward\", SettingController, :reward, as: :reward)\n    end\n  end\n\n  scope \"/admin\", MiphaWeb.Admin, as: :admin do\n    pipe_through(~w(browser admin)a)\n\n    get \"/\", PageController, :index\n    resources(\"/users\", UserController, only: ~w(index delete)a)\n    resources(\"/nodes\", NodeController)\n    resources(\"/topics\", TopicController, only: ~w(index)a)\n    resources(\"/replies\", ReplyController, only: ~w(index show delete)a)\n    resources(\"/companies\", CompanyController, only: ~w(index delete)a)\n    resources(\"/teams\", TeamController, only: ~w(index delete)a)\n    resources(\"/notifications\", NotificationController, only: ~w(index show delete)a)\n  end\n\n  # Other scopes may use custom stacks.\n  scope \"/api\", MiphaWeb do\n    pipe_through(:api)\n\n    post \"/topics/preview\", TopicController, :preview\n    post \"/callback/qiniu\", CallbackController, :qiniu\n  end\n\n  if Mix.env() == :dev do\n    forward \"/sent_emails\", Bamboo.SentEmailViewerPlug\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/session.ex",
    "content": "defmodule MiphaWeb.Session do\n  @moduledoc \"\"\"\n  Some helpers for session-related things\n  \"\"\"\n\n  alias Mipha.Accounts\n  alias Plug.Conn\n\n  def current_user(%{assigns: %{current_user: u}}), do: u\n\n  def current_user(conn) do\n    case get_current_user(conn) do\n      nil -> nil\n      id -> Accounts.get_user(id)\n    end\n  end\n\n  def user_logged_in?(conn), do: current_user(conn)\n\n  def user_token(conn) do\n    case get_current_user(conn) do\n      nil -> nil\n      id -> Phoenix.Token.sign(conn, \"user socket\", id)\n    end\n  end\n\n  def admin?(conn) do\n    case current_user(conn) do\n      nil -> false\n      u -> u.is_admin\n    end\n  end\n\n  defp get_current_user(conn) do\n    if get_session_from_cookies() do\n      case conn.cookies[\"current_user\"] do\n        nil -> Conn.get_session(conn, :current_user)\n        u -> u\n      end\n    else\n      Conn.get_session(conn, :current_user)\n    end\n  end\n\n  defp get_session_from_cookies do\n    Application.get_env(:mipha_web, :get_session_from_cookies, false)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/company/index.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <div class=\"toolbar\">\n      <%= form_for @conn, admin_company_path(@conn, :index), [method: :get, class: \"form-inline\"], fn f -> %>\n        <div class=\"form-group mr-sm-2\">\n          <%= turbo_search_input f, \"q[name_like]\", class: \"form-control btn-sm\", placeholder: \"名称\" %>\n        </div>\n        <button class=\"btn btn-primary btn-sm\">搜索</button>\n      <% end %>\n    </div>\n    <table class=\"table table-bordered table-hover table-sm text-center\">\n      <thead>\n        <tr>\n          <th>#</th>\n          <th>名称</th>\n          <th>所在城市</th>\n          <th>创建时间</th>\n          <th>操作</th>\n        </tr>\n      </thead>\n      <tbody>\n        <%= for company <- @companies do %>\n        <tr>\n          <td><%= company.id %></td>\n          <td><%= company.name %></td>\n          <td><%= company.location_id %></td>\n          <td><%= company.inserted_at %></td>\n          <td>\n            <span><%= link \"\", to: company_path(@conn, :show, company), class: \"fa fa-arrows-alt\" %></span>\n            <span><%= link \"\", to: admin_company_path(@conn, :delete, company), method: :delete, data: [confirm: \"Are you sure?\"], class: \"fa fa-trash\" %></span>\n          </td>\n        </tr>\n        <% end %>\n      </tbody>\n    </table>\n  </div>\n  <div class=\"card-footer\">\n    <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n  </div>\n</div>\n\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/node/edit.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <%= render \"form.html\", Map.put(assigns, :action, admin_node_path(@conn, :update, @node)) %>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/node/form.html.eex",
    "content": "<%= form_for @changeset, @action, fn f -> %>\n  <%= if @changeset.action do %>\n    <div class=\"alert alert-danger\">\n      <p>Oops, something went wrong! Please check the errors below.</p>\n    </div>\n  <% end %>\n\n  <div class=\"form-group\">\n    <%= label f, :name, class: \"control-label\" %>\n    <%= text_input f, :name, class: \"form-control\" %>\n    <%= error_tag f, :name %>\n  </div>\n\n  <div class=\"form-group\">\n    <%= label f, :summary, class: \"control-label\" %>\n    <%= text_input f, :summary, class: \"form-control\" %>\n    <%= error_tag f, :summary %>\n  </div>\n\n  <div class=\"form-group\">\n    <%= label f, :position, class: \"control-label\" %>\n    <%= number_input f, :position, class: \"form-control\" %>\n    <%= error_tag f, :position %>\n  </div>\n\n  <div class=\"form-group\">\n    <%= label f, :parent_id, class: \"control-label\" %>\n    <%= number_input f, :parent_id, class: \"form-control\" %>\n    <%= error_tag f, :parent_id %>\n  </div>\n\n  <div class=\"form-group\">\n    <%= submit \"Submit\", class: \"btn btn-primary\" %>\n  </div>\n<% end %>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/node/index.html.eex",
    "content": "<div class=\"card bg-light\">\n  <span><%= link \"新建节点\", to: admin_node_path(@conn, :new), class: \"btn btn-default btn-sm\" %></span>\n  <div class=\"toolbar\">\n    <%= form_for @conn, admin_node_path(@conn, :index), [method: :get, class: \"form-inline\"], fn f -> %>\n      <div class=\"form-group mr-sm-2\">\n        <%= turbo_search_input f, \"q[name_like]\", class: \"form-control btn-sm\", placeholder: \"名称\" %>\n      </div>\n      <button class=\"btn btn-primary btn-sm\">搜索</button>\n    <% end %>\n  </div>\n  <div class=\"card-body\">\n    <table class=\"table table-bordered table-hover table-sm text-center\">\n      <thead>\n        <tr>\n          <th>#</th>\n          <th>名称</th>\n          <th>概况</th>\n          <th>排序</th>\n          <th>父节点</th>\n          <th>创建时间</th>\n          <th>操作</th>\n        </tr>\n      </thead>\n      <tbody>\n        <%= for node <- @nodes do %>\n        <tr>\n          <td><%= node.id %></td>\n          <td><%= node.name %></td>\n          <td><%= node.summary %></td>\n          <td><%= node.position %></td>\n          <td><%= node.parent_id %></td>\n          <td><%= node.inserted_at %></td>\n          <td>\n            <span><%= link \"\", to: admin_node_path(@conn, :show, node), class: \"fa fa-arrows-alt\" %></span>\n            <span><%= link \"\", to: admin_node_path(@conn, :edit, node), class: \"fa fa-pencil\" %></span>\n            <span><%= link \"\", to: admin_node_path(@conn, :delete, node), method: :delete, data: [confirm: \"Are you sure?\"], class: \"fa fa-trash\" %></span>\n          </td>\n        </tr>\n        <% end %>\n      </tbody>\n    </table>\n  </div>\n  <div class=\"card-footer\">\n    <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/node/new.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <%= render \"form.html\", Map.put(assigns, :action, admin_node_path(@conn, :create)) %>\n  </div>\n</div>\n\n\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/node/show.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <ul>\n      <li>\n        <strong>Name:</strong>\n        <%= @node.name %>\n      </li>\n      <li>\n        <strong>Summary:</strong>\n        <%= @node.summary %>\n      </li>\n      <li>\n        <strong>Position:</strong>\n        <%= @node.position %>\n      </li>\n      <li>\n        <strong>Parent:</strong>\n        <%= @node.parent_id %>\n      </li>\n    </ul>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/admin/notification/index.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <table class=\"table table-bordered table-hover table-sm text-center\">\n      <thead>\n        <tr>\n          <th>#</th>\n          <th>类型</th>\n          <th>创建人</th>\n          <th>评论</th>\n          <th>用户</th>\n          <th>话题</th>\n          <th>创建时间</th>\n          <th>操作</th>\n        </tr>\n      </thead>\n      <tbody>\n        <%= for notification <- @notifications do %>\n        <tr>\n          <td><%= notification.id %></td>\n          <td><%= notification.action %></td>\n          <td><%= notification.actor_id %></td>\n          <td><%= notification.reply_id %></td>\n          <td><%= notification.user_id %></td>\n          <td><%= notification.topic_id %></td>\n          <td><%= notification.inserted_at %></td>\n          <td>\n            <span><%= link \"\", to: admin_notification_path(@conn, :show, notification), class: \"fa fa-arrows-alt\" %></span>\n            <span><%= link \"\", to: admin_notification_path(@conn, :delete, notification), method: :delete, data: [confirm: \"Are you sure?\"], class: \"fa fa-trash\" %></span>\n          </td>\n        </tr>\n        <% end %>\n      </tbody>\n    </table>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/notification/show.html.eex",
    "content": "<h2>Show Notification</h2>\n\n<ul>\n\n  <li>\n    <strong>Action:</strong>\n    <%= @notification.action %>\n  </li>\n\n  <li>\n    <strong>Actor:</strong>\n    <%= @notification.actor_id %>\n  </li>\n\n  <li>\n    <strong>Reply:</strong>\n    <%= @notification.reply_id %>\n  </li>\n\n  <li>\n    <strong>User:</strong>\n    <%= @notification.user_id %>\n  </li>\n\n  <li>\n    <strong>Topic:</strong>\n    <%= @notification.topic_id %>\n  </li>\n\n</ul>\n\n<span><%= link \"Edit\", to: admin_notification_path(@conn, :edit, @notification) %></span>\n<span><%= link \"Back\", to: admin_notification_path(@conn, :index) %></span>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/page/index.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <h3>Admin Dashboard</h3>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/admin/reply/index.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <div class=\"toolbar\">\n      <%= form_for @conn, admin_reply_path(@conn, :index), [method: :get, class: \"form-inline\"], fn f -> %>\n        <div class=\"form-group mr-sm-2\">\n          <%= turbo_search_input f, \"q[content_like]\", class: \"form-control btn-sm\", placeholder: \"内容\" %>\n        </div>\n        <button class=\"btn btn-primary btn-sm\">搜索</button>\n      <% end %>\n    </div>\n    <table class=\"table table-bordered table-hover table-sm text-center\">\n      <thead>\n        <tr>\n          <th>#</th>\n          <th>用户</th>\n          <th>内容</th>\n          <th>帖子</th>\n          <th>父评论</th>\n          <th>创建时间</th>\n          <th>操作</th>\n        </tr>\n      </thead>\n      <tbody>\n        <%= for reply <- @replies do %>\n        <tr>\n          <td><%= reply.id %></td>\n          <td><%= reply.user_id %></td>\n          <td><%= reply.content %></td>\n          <td><%= reply.topic_id %></td>\n          <td><%= reply.parent_id %></td>\n          <td><%= reply.inserted_at %></td>\n          <td>\n            <span><%= link \"\", to: admin_reply_path(@conn, :show, reply), class: \"fa fa-arrows-alt\" %></span>\n            <span><%= link \"\", to: admin_reply_path(@conn, :delete, reply), method: :delete, data: [confirm: \"Are you sure?\"], class: \"fa fa-trash\" %></span>\n          </td>\n        </tr>\n        <% end %>\n      </tbody>\n    </table>\n  </div>\n  <div class=\"card-footer\">\n    <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/reply/show.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <ul>\n      <li>\n        <strong>Topic:</strong>\n        <%= @reply.topic_id %>\n      </li>\n      <li>\n        <strong>User:</strong>\n        <%= @reply.user_id %>\n      </li>\n      <li>\n        <strong>Parent:</strong>\n        <%= @reply.parent_id %>\n      </li>\n      <li>\n        <strong>Content:</strong>\n        <%= @reply.content %>\n      </li>\n    </ul>\n  </div>\n</div>\n\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/team/edit.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <%= render \"form.html\", Map.put(assigns, :action, admin_team_path(@conn, :update, @team)) %>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/team/form.html.eex",
    "content": "<%= form_for @changeset, @action, fn f -> %>\n  <%= if @changeset.action do %>\n    <div class=\"alert alert-danger\">\n      <p>Oops, something went wrong! Please check the errors below.</p>\n    </div>\n  <% end %>\n\n  <div class=\"form-group\">\n    <%= label f, :owner_id, class: \"control-label\" %>\n    <%= number_input f, :owner_id, class: \"form-control\" %>\n    <%= error_tag f, :owner_id %>\n  </div>\n\n  <div class=\"form-group\">\n    <%= label f, :github_handle, class: \"control-label\" %>\n    <%= text_input f, :github_handle, class: \"form-control\" %>\n    <%= error_tag f, :github_handle %>\n  </div>\n\n  <div class=\"form-group\">\n    <%= label f, :name, class: \"control-label\" %>\n    <%= text_input f, :name, class: \"form-control\" %>\n    <%= error_tag f, :name %>\n  </div>\n\n  <div class=\"form-group\">\n    <%= label f, :summary, class: \"control-label\" %>\n    <%= text_input f, :summary, class: \"form-control\" %>\n    <%= error_tag f, :summary %>\n  </div>\n\n  <div class=\"form-group\">\n    <%= label f, :avatar, class: \"control-label\" %>\n    <%= text_input f, :avatar, class: \"form-control\" %>\n    <%= error_tag f, :avatar %>\n  </div>\n\n  <div class=\"form-group\">\n    <%= submit \"Submit\", class: \"btn btn-primary\" %>\n  </div>\n<% end %>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/team/index.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <div class=\"toolbar\">\n      <%= form_for @conn, admin_team_path(@conn, :index), [method: :get, class: \"form-inline\"], fn f -> %>\n        <div class=\"form-group mr-sm-2\">\n          <%= turbo_search_input f, \"q[name_like]\", class: \"form-control btn-sm\", placeholder: \"名称\" %>\n        </div>\n        <button class=\"btn btn-primary btn-sm\">搜索</button>\n      <% end %>\n    </div>\n    <table class=\"table table-bordered table-hover table-sm text-center\">\n      <thead>\n        <tr>\n          <th>#</th>\n          <th>名称</th>\n          <th>拥有者</th>\n          <th>创建时间</th>\n          <th>操作</th>\n        </tr>\n      </thead>\n      <tbody>\n        <%= for team <- @teams do %>\n        <tr>\n          <td><%= team.id %></td>\n          <td><%= team.name %></td>\n          <td><%= team.owner_id %></td>\n          <td><%= team.inserted_at %></td>\n          <td>\n            <span><%= link \"\", to: team_path(@conn, :show, team), class: \"fa fa-arrows-alt\" %></span>\n            <span><%= link \"\", to: admin_team_path(@conn, :delete, team), method: :delete, data: [confirm: \"Are you sure?\"], class: \"fa fa-trash\" %></span>\n          </td>\n        </tr>\n        <% end %>\n      </tbody>\n    </table>\n  </div>\n  <div class=\"card-footer\">\n    <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/team/new.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <%= render \"form.html\", Map.put(assigns, :action, admin_team_path(@conn, :create)) %>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/team/show.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <ul>\n      <li>\n        <strong>Owner:</strong>\n        <%= @team.owner_id %>\n      </li>\n      <li>\n        <strong>Github handle:</strong>\n        <%= @team.github_handle %>\n      </li>\n      <li>\n        <strong>Name:</strong>\n        <%= @team.name %>\n      </li>\n      <li>\n        <strong>Summary:</strong>\n        <%= @team.summary %>\n      </li>\n      <li>\n        <strong>Avatar:</strong>\n        <%= @team.avatar %>\n      </li>\n    </ul>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/topic/index.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <div class=\"toolbar\">\n      <%= form_for @conn, admin_topic_path(@conn, :index), [method: :get, class: \"form-inline\"], fn f -> %>\n        <div class=\"form-group mr-sm-2\">\n          <%= turbo_search_input f, \"q[title_like]\", class: \"form-control btn-sm\", placeholder: \"标题\" %>\n        </div>\n        <button class=\"btn btn-primary btn-sm\">搜索</button>\n      <% end %>\n    </div>\n    <table class=\"table table-bordered table-hover table-sm text-center\">\n      <thead>\n        <tr>\n          <th>#</th>\n          <th>标题</th>\n          <th>作者</th>\n          <th>类型</th>\n          <th>节点</th>\n          <th>访问次数</th>\n          <th>评论次数</th>\n          <th>点赞次数</th>\n          <th>创建时间</th>\n          <th>操作</th>\n        </tr>\n      </thead>\n      <tbody>\n        <%= for topic <- @topics do %>\n        <tr>\n          <td><%= topic.id %></td>\n          <td><%= topic.title %></td>\n          <td><%= topic.user_id %></td>\n          <td><%= topic.type %></td>\n          <td><%= topic.node_id %></td>\n          <td><%= topic.visit_count %></td>\n          <td><%= topic.reply_count %></td>\n          <td><%= topic.star_count %></td>\n          <td><%= topic.inserted_at %></td>\n          <td>\n            <span><%= link \"\", to: topic_path(@conn, :show, topic), class: \"fa fa-arrows-alt\" %></span>\n          </td>\n        </tr>\n        <% end %>\n      </tbody>\n    </table>\n  </div>\n  <div class=\"card-footer\">\n    <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n  </div>\n</div>\n\n"
  },
  {
    "path": "lib/mipha_web/templates/admin/user/index.html.eex",
    "content": "<div class=\"card bg-light\">\n  <div class=\"card-body\">\n    <div class=\"toolbar\">\n      <%= form_for @conn, admin_user_path(@conn, :index), [method: :get, class: \"form-inline\"], fn f -> %>\n        <div class=\"form-group mr-sm-2\">\n          <%= turbo_search_input f, \"q[username_or_email_like]\", class: \"form-control btn-sm\", placeholder: \"用户名\" %>\n        </div>\n        <button class=\"btn btn-primary btn-sm\">搜索</button>\n      <% end %>\n    </div>\n    <table class=\"table table-bordered table-hover table-sm text-center\">\n      <thead>\n        <tr>\n          <th>#</th>\n          <th>用户名</th>\n          <th>邮箱</th>\n          <th>注册时间</th>\n          <th>操作</th>\n        </tr>\n      </thead>\n      <tbody>\n        <%= for user <- @users do %>\n        <tr>\n          <td><%= user.id %></td>\n          <td><%= user.username %></td>\n          <td><%= user.email %></td>\n          <td><%= user.inserted_at %></td>\n          <td>\n            <span><%= link \"\", to: user_path(@conn, :show, user.username), class: \"fa fa-arrows-alt\" %></span>\n          </td>\n        </tr>\n        <% end %>\n      </tbody>\n    </table>\n  </div>\n  <div class=\"card-footer\">\n    <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/auth/login.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col\"></div>\n  <div class=\"col-md-5\">\n    <div class=\"card\">\n      <div class=\"card-header\"><%= gettext \"Sign In\" %></div>\n      <div class=\"card-body\">\n        <%= form_for @changeset, \"/auth/identity/callback\", fn f -> %>\n          <div class=\"form-group\">\n            <%= text_input f, :login, class: \"form-control\", required: true, tabindex: 1, placeholder: \"Username / Email\" %>\n            <%= error_tag f, :login %>\n          </div>\n          <div class=\"form-group\">\n            <%= password_input f, :password, class: \"form-control\", required: true, tabindex: 2, placeholder: \"Password\" %>\n          </div>\n          <div class=\"form-group from-check\">\n            <label class=\"form-check-label\" for=\"user_remember_me\">\n              <input name=\"user[remember_me]\" type=\"hidden\" value=\"0\" /><input type=\"checkbox\" value=\"1\" name=\"user[remember_me]\" id=\"user_remember_me\" />\n              <%= gettext \"Remember me (2 months)\" %>\n            </label>\n          </div>\n          <div class=\"form-group text-center\">\n            <%= submit class: \"btn btn-primary btn-block btn-lg\", tabindex: 3 do %>\n              <span><%= gettext \"Sign In\" %></span>\n            <% end %>\n          </div>\n        <% end %>\n      </div>\n    </div>\n  </div>\n  <div class=\"col-md-3\">\n    <div class=\"card\">\n      <div class=\"card-header\"><%= gettext \"Sign in with other services\" %></div>\n      <ul class=\"list-group list-group-flush\">\n        <li class=\"list-group-item\">\n          <a class=\"btn btn-default btn-lg btn-block\" href=\"/auth/github\"><i class='fa fa-github'></i><%= gettext \"Github\" %></a>\n        </li>\n      </ul>\n    </div>\n\n    <div class=\"card\">\n      <ul class=\"list-group list-group-flush\">\n        <li class=\"list-group-item\"><a href=\"/join\"><%= gettext \"Register\" %></a></li>\n        <li class=\"list-group-item\"><a href=\"/forgot_password\"><%= gettext \"Forgot your password?\" %></a></li>\n      </ul>\n    </div>\n  </div>\n  <div class=\"col\"></div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/company/index.html.eex",
    "content": "<h1>Company Index Page</h1>"
  },
  {
    "path": "lib/mipha_web/templates/company/show.html.eex",
    "content": "<h1>Company Show Page</h1>"
  },
  {
    "path": "lib/mipha_web/templates/email/forgot_password.html.eex",
    "content": "<p class=\"lead\">你好 <%= @user.username %></p>\n<p>有人请求找回在 Elixir Mipha 上面，账号为 <span class=\"text-danger\"><%= @user.username %></span> 的密码，如果你是，那么你可以通过下面的连接进入网站修改密码。</p>\n<p><%= link \"修改我的密码\", to: user_url(MiphaWeb.Endpoint, :reset_password, token: @token), class: \"btn\" %></p>\n<p>如果看不到连接，请复制下面的连接然后在浏览器里面打开：</p>\n<p><%= user_url(MiphaWeb.Endpoint, :reset_password, token: @token) %></p>\n<p class=\"text-muted\">如果你没有申请，可以忽略这封邮件，你的账号不会受到影响。</p>\n"
  },
  {
    "path": "lib/mipha_web/templates/email/verify_email.html.eex",
    "content": "<p class=\"lead\">欢迎 <%= @user.username %></p>\n<p>你已经成功注册 Elixir Mipha 的账号，\n  接下来你需要点击下面的连接 <span class=\"text-danger\">激活</span> 你的账号：</p>\n<p><%= link \"激活账号\", to: user_url(MiphaWeb.Endpoint, :verify_email, token: @token), class: \"btn\" %></p>\n<p>如果看不到链接，请复制下面的连接然后在浏览器里面打开：</p>\n<p><%= user_url(MiphaWeb.Endpoint, :verify_email, token: @token) %></p>\n"
  },
  {
    "path": "lib/mipha_web/templates/email/welcome.html.eex",
    "content": "<p class=\"lead\">你好 <%= @user.username %></p>\n<p>你已经成功在 <%= link \"Elixir Mipha\", to: topic_url(MiphaWeb.Endpoint, :index) %> 注册了账号。</p>\n欢迎加入 Elixir Mipha\n<p><%= link \"立即登录\", to: join_url(MiphaWeb.Endpoint, :new), class: \"btn btn-primary\" %></p>\n<p>登录页面地址：<%= link join_url(MiphaWeb.Endpoint, :new), to: join_url(MiphaWeb.Endpoint, :new) %></p>\n"
  },
  {
    "path": "lib/mipha_web/templates/layout/_flash.html.eex",
    "content": "<div class=\"container\" style=\"margin-top: 10px;\">\n  <%= for item <- ~w(danger success warning info)a do %>\n    <%= if get_flash(@conn, item) do %>\n    <div class=\"alert-<%= item %> alert text-center alert-dismissible fade show\" role=\"alert\">\n      <%= get_flash(@conn, item) %>\n      <button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\">\n        <span aria-hidden=\"true\">&times;</span>\n      </button>\n    </div>\n    <% end %>\n  <% end %>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/layout/_footer.html.eex",
    "content": "<footer class=\"footer\" id=\"footer\">\n  <div class=\"container\">\n    <div id=\"online-users\">\n    </div>\n  </div>\n</footer>"
  },
  {
    "path": "lib/mipha_web/templates/layout/_header.html.eex",
    "content": "<div class=\"header navbar navbar-expand flex-md-row bd-navbar\">\n  <div class=\"container d-sm-flex\">\n    <div class=\"navbar-header d-none d-md-flex\" id=\"navbar-header\">\n      <a href=\"/\" class=\"navbar-brand\">\n        <b>Elixir</b> Mipha</a>\n    </div>\n    <div class=\"navbar-nav-scroll\">\n      <ul id=\"main-nav-menu\" class=\"navbar-nav d-flex mi-nav\">\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"/topics\"><%= gettext \"Topic\" %></a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"/jobs\"><%= gettext \"Job\" %></a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"/users\"><%= gettext \"User\" %></a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"https://github.com/zven21/mipha\" target=\"_blank\"><%= gettext \"Source Code\" %></a>\n        </li>\n      </ul>\n    </div>\n    <div class=\"ml-auto d-md-flex\">\n      <ul id=\"main-nav-menu\" class=\"navbar-nav d-flex\">\n        <li class=\"nav-item\">\n          <a href=\"?locale=en\" class=\"nav-link\"> English </a>\n        </li>\n        <li class=\"nav-item\">\n          <a href=\"?locale=zh\" class=\"nav-link\"> 简体中文 </a>\n        </li>\n      </ul>\n      <%# <form class=\"navbar-form d-none d-lg-flex mr-2 form-search active\" action=\"/search\" method=\"GET\">\n        <i class=\"fa btn-search fa-search\"></i>\n        <input class=\"form-control\" name=\"q\" type=\"text\" value=\"\" placeholder=\"搜索本站内容\">\n      </form> %>\n      <ul class=\"nav navbar-nav user-bar align-items-center justify-content-end\">\n        <%= if user_logged_in?(@conn) do %>\n          <%= if has_unread_notification?(@current_user) do %>\n            <li class=\"nav-item notification-count\">\n              <a href=\"/notifications\" class=\"nav-link new\" title=\"通知\">\n                <i class=\"fa fa-bell\"></i>\n                <span class=\"count\"><%= unread_notification_count(@current_user) %></span>\n              </a>\n            </li>\n          <% end %>\n        <li class=\"nav-item dropdown\">\n          <a href=\"#\" id=\"navbar-new-menu\" class=\"nav-link dropdown-toggle\" role=\"button\" data-toggle=\"dropdown\" aria-haspopup=\"true\"\n            aria-expanded=\"false\">\n            <i class=\"fa fa-plus\"></i>\n          </a>\n          <div class=\"dropdown-menu dropdown-menu-right\" aria-labelledby=\"navbar-new-menu\">\n            <a class=\"dropdown-item\" href=\"/topics/new\"><%= gettext \"New Topic\" %></a>\n          </div>\n        </li>\n\n        <li class=\"nav-item dropdown dropdown-avatar\">\n          <a href=\"#\" class=\"dropdown-toggle nav-link\" id=\"navbar-user-menu\" data-toggle=\"dropdown\" role=\"button\" aria-expanded=\"false\">\n            <img class=\"media-object avatar-32\" src=\"<%= qn_url(@current_user.avatar) %>\">\n          </a>\n          <div class=\"dropdown-menu dropdown-menu-right\" aria-labelledby=\"navbar-user-menu\">\n            <a class=\"dropdown-item\" href=\"/u/<%= @current_user.username %>\"><%= @current_user.username %></a>\n            <div class=\"dropdown-divider\"></div>\n            <a class=\"dropdown-item\" href=\"/setting\"><%= gettext \"Account Profile\" %></a>\n\n            <div class=\"dropdown-divider\"></div>\n            <%= link (gettext \"Sign Out\"), to: logout_path(@conn, :delete), class: \"dropdown-item\" %>\n          </div>\n        </li>\n        <% else %>\n          <li class=\"nav-item\" ><%= link (gettext \"Register\"), to: join_path(@conn, :new), class: \"nav-link\" %></li>\n          <li class=\"nav-item\" ><%= link (gettext \"Sign In\"), to: auth_path(@conn, :login), class: \"nav-link\" %></li>\n        <% end %>\n        </ul>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/layout/_navbar.html.eex",
    "content": " <div class=\"header\">\n    <div class=\"header navbar navbar-expand flex-md-row bd-navbar\">\n      <div class=\"container d-sm-flex\">\n        <div class=\"navbar-header d-none d-md-flex\" id=\"navbar-header\">\n          <%= link(\"控制台\", to: admin_page_path(@conn, :index), class: \"navbar-brand\") %>\n        </div>\n        <div class=\"navbar-nav-scroll\">\n          <ul class=\"mr-auto nav navbar-nav mi-nav\">\n            <li class=\"nav-item\"><%= link \"概况\", to: admin_page_path(@conn, :index), class: \"nav-link\" %></li>\n            <li class=\"nav-item\"><%= link \"用户\", to: admin_user_path(@conn, :index), class: \"nav-link\" %></li>\n            <li class=\"nav-item\"><%= link \"团队\", to: admin_team_path(@conn, :index), class: \"nav-link\" %></li>\n            <li class=\"nav-item\"><%= link \"话题\", to: admin_topic_path(@conn, :index), class: \"nav-link\" %></li>\n            <li class=\"nav-item\"><%= link \"节点\", to: admin_node_path(@conn, :index), class: \"nav-link\" %></li>\n            <li class=\"nav-item\"><%= link \"评论\", to: admin_reply_path(@conn, :index), class: \"nav-link\" %></li>\n            <li class=\"nav-item\"><%= link \"公司\", to: admin_company_path(@conn, :index), class: \"nav-link\" %></li>\n          </ul>\n        </div>\n        <div class=\"ml-auto d-md-flex\">\n        </div>\n      </div>\n    </div>\n  </div>"
  },
  {
    "path": "lib/mipha_web/templates/layout/_sub_header.html.eex",
    "content": "<div class=\"sub-navbar node-header hide-ios\">\n  <div class=\"container\">\n    <%= render MiphaWeb.TopicView, \"_node_selector.html\", assigns %>\n    <ul class=\"filter nav nav-pills mi-nav\">\n      <li>\n        <a href=\"#\" class=\"all-nodes\" data-toggle=\"modal\" data-target=\"#node-selector\">\n          <%= gettext \"All Nodes\" %>\n          <span class=\"caret-right\"></span>\n        </a>\n      </li>\n      <li><a href=\"/topics\"><%= gettext \"Normal\" %></a></li>\n      <li><a href=\"/topics/featured\"><i class=\"fa fa-diamond\"></i><%= gettext \"Featured\" %></a></li>\n      <li><a href=\"/topics/popular\"></i><%= gettext \"Popular\" %></a></li>\n      <li><a href=\"/topics/no_reply\"><%= gettext \"No Reply\" %></a></li>\n    </ul>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/layout/admin.html.eex",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"description\" content=\"\">\n    <meta name=\"author\" content=\"\">\n\n    <title>Admin Dashboard</title>\n    <link rel=\"stylesheet\" href=\"<%= static_path(@conn, \"/css/admin.css\") %>\">\n  </head>\n\n  <body>\n    <%= render \"_navbar.html\", assigns %>\n    <div class=\"container\">\n      <%= render \"_flash.html\", assigns %>\n      <main role=\"main\">\n        <%= @inner_content %>\n      </main>\n    </div>\n    <script src=\"<%= static_path(@conn, \"/js/admin.js\") %>\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "lib/mipha_web/templates/layout/app.html.eex",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"description\" content=\"\">\n    <meta name=\"author\" content=\"\">\n\n    <title>Elixir Mipha</title>\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"<%= static_path(@conn, \"/favicon.ico\") %>\">\n    <link rel=\"stylesheet\" href=\"<%= static_path(@conn, \"/css/app.css\") %>\">\n  </head>\n\n  <body>\n    <%= render \"_header.html\", assigns %>\n    <%= if Map.get(assigns, :asset) == \"topics\" do %>\n      <%= render \"_sub_header.html\", assigns %>\n    <% end %>\n    <%= render \"_flash.html\", assigns %>\n    <div id=\"main\" class=\"main-container container\">\n      <%= @inner_content %>\n    </div>\n    <%= render \"_footer.html\", assigns %>\n\n    <script>window.userToken = \"<%= assigns[:user_token] %>\"</script>\n    <script src=\"<%= static_path(@conn, \"/js/app.js\") %>\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "lib/mipha_web/templates/layout/email.html.eex",
    "content": "<html>\n  <head>\n  </head>\n  <body>\n    <%= @inner_content %>\n  </body>\n</html>\n"
  },
  {
    "path": "lib/mipha_web/templates/location/index.html.eex",
    "content": "<h1>Locations Index Page</h1>"
  },
  {
    "path": "lib/mipha_web/templates/location/show.html.eex",
    "content": "<div id=\"hot_users\" class=\"card user-list\">\n  <div class=\"card-header\"><%= @location.name %>的伙伴</div>\n  <div class=\"card-body row\">\n    <%= for item <- @location.users do %>\n      <div class=\"user col-sm-1\">\n        <div class=\"avatar\">\n          <%= link to: user_path(@conn, :show, item.username), title: item.username do %>\n            <img class=\"media-object avatar-48\" src=\"<%= qn_url(item.avatar) %>\">\n          <% end %>\n        </div>\n        <div class=\"name\">\n          <%= link item.username, to: user_path(@conn, :show, item.username), title: item.username %>\n        </div>\n      </div>\n    <% end %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/notification/_followed.html.eex",
    "content": "<%= if is_nil(@target_object) do %>\n  <div class=\"media-body\">相关信息已删除</div>\n<% else %>\n  <div class=\"media-heading\">\n    <%= user_name_tag(@actor) %> 开始关注你了。\n  </div>\n<% end %>\n"
  },
  {
    "path": "lib/mipha_web/templates/notification/_reply_comment_added.html.eex",
    "content": "<%= if is_nil(@target_object) || is_nil(@target_object.topic) do %>\n  <div class=\"media-body\">相关信息已删除</div>\n<% else %>\n  <div class=\"media-heading\">\n    <span class=\"info\">在帖子 <%= topic_title_tag(@target_object.topic, @target_object) %> 回复了你的评论：</span>\n    <div class=\"media-content summary markdown\">\n      <%= markdown(@target_object.content) %>\n    </div>\n  </div>\n<% end %>\n"
  },
  {
    "path": "lib/mipha_web/templates/notification/_reply_mentioned.html.eex",
    "content": "<%= if is_nil(@target_object) || is_nil(@target_object.topic) do %>\n  <div class=\"media-body\">相关信息已删除</div>\n<% else %>\n  <div class=\"media-heading\">\n    <span class=\"info\">在 <%= topic_title_tag(@target_object.topic) %> 提及你：</span>\n    <div class=\"media-content summary markdown\">\n      <%= markdown(@target_object.content) %>\n    </div>\n  </div>\n<% end %>"
  },
  {
    "path": "lib/mipha_web/templates/notification/_reply_starred.html.eex",
    "content": "<%= if is_nil(@target_object) || is_nil(@target_object.topic) do %>\n  <div class=\"media-body\">相关信息已删除</div>\n<% else %>\n  <div class=\"media-heading\">\n    你在帖子中 <%= topic_title_tag(@target_object.topic, @target_object) %> 的评论被赞了。\n  </div>\n<% end %>"
  },
  {
    "path": "lib/mipha_web/templates/notification/_topic_added.html.eex",
    "content": "<%= if is_nil(@target_object) do %>\n  <div class=\"media-body\">相关信息已删除</div>\n<% else %>\n  <div class=\"media-heading\">\n    创建了帖子 <%= topic_title_tag(@target_object) %>\n  </div>\n<% end %>"
  },
  {
    "path": "lib/mipha_web/templates/notification/_topic_mentioned.html.eex",
    "content": "<% if is_nil(@target_object) do %>\n  <div class=\"media-body\">相关信息已删除</div>\n<% else %>\n  <div class=\"media-heading\">\n    <span class=\"info\">在 <%= topic_title_tag(@target_object) %> 提及你：</span>\n    <div class=\"media-content summary markdown\">\n      <%= markdown(@target_object.body) %>\n    </div>\n  </div>\n<% end %>\n"
  },
  {
    "path": "lib/mipha_web/templates/notification/_topic_reply_added.html.eex",
    "content": "<%= if is_nil(@target_object) || is_nil(@target_object.topic) do %>\n  <div class=\"media-body\">相关信息已删除</div>\n<% else %>\n  <div class=\"media-heading\">\n    <span class=\"info\">在帖子 <%= topic_title_tag(@target_object.topic) %> 回复了：</span>\n    <div class=\"media-content summary markdown\">\n      <%= markdown(@target_object.content) %>\n    </div>\n  </div>\n<% end %>"
  },
  {
    "path": "lib/mipha_web/templates/notification/_topic_starred.html.eex",
    "content": "<%= if is_nil(@target_object) do %>\n  相关信息已删除\n<% else %>\n  <div class=\"media-heading\">\n    你的帖子 <%= topic_title_tag(@target_object) %> 被赞了。\n  </div>\n<% end %>"
  },
  {
    "path": "lib/mipha_web/templates/notification/index.html.eex",
    "content": "<div class=\"notifications card\">\n  <div class=\"card-header hide-ios clearfix\">\n    <%= gettext \"Notifications\" %>\n    <span class=\"pull-right\">\n      <%= link gettext(\"Mark all readed\"), to: notification_path(@conn, :make_read), method: :post, class: \"btn btn-sm btn-default\" %>\n      <%= link gettext(\"Clean all\"), to: notification_path(@conn, :clean), method: :delete, class: \"btn btn-sm btn-danger\" %>\n    </span>\n  </div>\n  <div class=\"card-body list item-list\">\n    <%= for {key, value} <- group_by(@notifications) do %>\n      <div class=\"notification-group\">\n        <div class=\"group-title\">\n          <i class=\"fa fa-clock-o\"></i>\n          <%= key %>\n        </div>\n        <%= for item <- value do %>\n          <div class=\"media notification\">\n            <div class=\"media-left mr-3\">\n              <%= link to: user_path(@conn, :show, item.user.username), title: item.user.username do %>\n                <img src=\"<%= qn_url(item.user.avatar) %>\" class=\"media-object avatar-48\">\n              <% end %>\n            </div>\n            <div class=\"media-body\">\n              <%= transfer_action(item.notification) %>\n            </div>\n            <div class=\"media-right\">\n              <%= hour_format(item) %>\n            </div>\n          </div>\n        <% end %>\n      </div>\n    <% end %>\n  </div>\n  <div class=\"card-footer hide-ios\">\n    <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/page/_locations.html.eex",
    "content": "<div class=\"index-locations card\">\n  <div class=\"card-header\">\n    <%= gettext \"Hot Cities\" %>\n  </div>\n  <div class=\"card-body location-list\" style=\"text-align:center;\">\n    <%= for item <- @locations do %>\n      <span class=\"name\">\n        <%= link item.name, to: location_path(@conn, :show, item) %>\n      </span>\n    <% end %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/page/index.html.eex",
    "content": "<%#= render \"_nodes.html\", assigns %>\n<%#= render \"_locations.html\", assigns %>\n\n"
  },
  {
    "path": "lib/mipha_web/templates/page/markdown.html.eex",
    "content": "<div class=\"card markdown\">\n  <div class=\"card-body\">\n    <h1><%= gettext \"Markdown Guide\" %></h1>\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <pre><code><%= @markdown_ex %></code></pre>\n      </div>\n      <div class=\"col-md-6\">\n        <%= markdown(@markdown_ex)%>\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/reply/edit.html.eex",
    "content": "<div>\n  <div class=\"edit-reply card\">\n    <div class=\"card-header\">\n      <%= gettext \"Edit Reply\" %>\n      <span class=\"pull-right\">\n        <%= link @topic.title, to: topic_path(@conn, :show, @topic), title: @topic.title %>\n      </span>\n    </div>\n    <div class=\"card-body\">\n      <%= form_for @changeset, topic_reply_path(@conn, :update, @topic, @reply), fn f -> %>\n        <%= render MiphaWeb.TopicView, \"_editor_toolbar.html\", assigns %>\n        <input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" />\n        <div class=\"form-group\">\n          <%= textarea f, :content, class: \"form-control topic-editor closewarning\", required: true, placeholder: \"\", rows: 10 %>\n        </div>\n        <div class=\"form-actions hide-ios\">\n          <%= submit gettext(\"Save\"), class: \"btn btn-primary col-xs-2 hide-ios\" %>\n          <%= link gettext(\"Delete\"), to: topic_reply_path(@conn, :delete, @topic, @reply), class: \"btn btn-danger\", method: :delete, data: [confirm: \"Are you sure？\"] %>\n        </div>\n      <% end %>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/search/index.html.eex",
    "content": "<div class=\"card\">\n  <h1>Search Page</h1>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/session/new.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col\"></div>\n  <div class=\"col-lg-6\">\n    <div class=\"card\">\n      <div class=\"card-header\"><%= gettext \"Register\" %></div>\n      <div class=\"card-body\">\n        <%= form_for @changeset, join_path(@conn, :create), fn f -> %>\n          <%= if @changeset.action do %>\n            <div class=\"alert alert-danger\">\n              <p>Oops, something went wrong! Please check the errors below.</p>\n            </div>\n          <% end %>\n          <div class=\"form-group\">\n            <%= text_input f, :username, class: \"form-control\", required: true, placeholder: \"Username\", tabindex: 1 %>\n            <%= error_tag f, :username %>\n          </div>\n          <div class=\"form-group\">\n            <%= text_input f, :email, class: \"form-control\", required: true, placeholder: \"Email\", tabindex: 2 %>\n            <%= error_tag f, :email %>\n          </div>\n          <div class=\"form-group\">\n            <%= password_input f, :password, class: \"form-control\", required: true, placeholder: \"Password\", tabindex: 3 %>\n          </div>\n          <div class=\"form-group\">\n            <div class=\"input-group\">\n              <input class=\"form-control input-lg\" placeholder=\"验证码\" name=\"_excaptcha\" type=\"text\" autocorrect=\"off\" autocapitalize=\"off\" pattern=\"[a-zA-Z]*\" maxlength=\"5\" autocomplete=\"off\">\n              <span class=\"input-group-addon input-group-captcha\"><a class=\"excaptcha-image-box\" href=\"#\"><img class=\"excaptcha-image\" src=\"<%= session_path(@conn, :excaptcha) %>\"></a></span>\n            </div>\n          </div>\n          <div class=\"form-group text-center\">\n            <%= submit class: \"btn btn-primary btn-block btn-lg\", tabindex: 4 do %>\n              <i class=\"fa fa-sign-in\" aria-hidden=\"true\"></i>\n              <span><%= gettext \"Register\" %></span>\n            <% end %>\n          </div>\n        <% end %>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\"></div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/setting/_sidebar.html.eex",
    "content": "<div class=\"card\">\n  <ul class=\"nav nav-pills flex-column nav-stacked mi-nav\">\n    <li class=\"nav-item\">\n      <%= link to: setting_path(@conn, :show), class: \"nav-link\" do %>\n        <i class='fa fa-user-circle'></i>\n        <span><%= gettext \"Profile\" %></span>\n      <% end %>\n    </li>\n    <li class=\"nav-item\">\n      <%= link to: setting_profile_path(@conn, :profile), class: \"nav-link hide-ios\" do %>\n        <i class='fa fa-address-card'></i>\n        <span><%= gettext \"Details\" %></span>\n      <% end %>\n    </li>\n    <li class=\"nav-item\">\n      <%= link to: setting_password_path(@conn, :password), class: \"nav-link hide-ios\" do %>\n        <i class='fa fa-keyboard-o'></i>\n        <span><%= gettext \"Change Password\" %></span>\n      <% end %>\n    </li>\n    <li class=\"nav-item\">\n      <%= link to: setting_reward_path(@conn, :reward), class: \"nav-link hide-ios\" do %>\n        <i class='fa fa-qrcode'></i>\n        <span><%= gettext \"Reward\" %></span>\n      <% end %>\n    </li>\n    <li class=\"nav-item\">\n      <%= link to: setting_account_path(@conn, :account), class: \"nav-link hide-ios\" do %>\n        <i class='fa fa-gear'></i>\n        <span><%= gettext \"Setting\" %></span>\n      <% end %>\n    </li>\n  </ul>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/setting/account.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-sm-2 col-md-offset-1 setting-menu\">\n    <%= render \"_sidebar.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-10\">\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <div class=\"heading\">删除我的账号(暂不提供该功能)</div>\n\n        <form action=\"/setting\" accept-charset=\"UTF-8\" data-remote=\"true\" method=\"post\">\n          <input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" />\n          <input type=\"hidden\" name=\"_method\" value=\"delete\" />\n          <input type=\"hidden\" name=\"authenticity_token\" value=\"FiuBKuNbeu5y1/Mrh8+7MhR7TjIa4/H6C9iIBKHB4C2+v0Jx0xLiOOoCi1ZakrMvgE8HavcalXlJwOJLNQXg6g==\"\n          />\n\n          <input type=\"hidden\" name=\"by\" value=\"destroy\" />\n          <div class=\"form-group\">\n            <p>当然不在想使用这个账号的时候，可以选择删除账号。</p>\n            <p>删除以后数据将会保留，以匿名的方式存在，你的账号将无法再登陆。</p>\n          </div>\n          <div class=\"form-group\">\n            <input class=\"form-control\" placeholder=\"要删除账号，请输入你的密码\" type=\"password\" name=\"user[current_password]\" id=\"user_current_password\"\n            />\n          </div>\n          <input type=\"submit\" name=\"commit\" value=\"确定删除我的账号\" class=\"btn btn-danger\" data-disable-with=\"正在提交\" />\n        </form>\n        <div class=\"heading\">绑定其他帐号用于登录</div>\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/setting/password.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-sm-2 col-md-offset-1 setting-menu\">\n    <%= render \"_sidebar.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-10\">\n    <div class=\"alert alert-warning\">\n      <%= gettext \"Note! You will need to log in again after updating your password.\" %>\n    </div>\n\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <%= form_for @changeset, setting_path(@conn, :update), fn f -> %>\n          <%= hidden_input f, :by, value: \"password\" %>\n\n          <div class=\"form-group\">\n            <%= label f, :current_password, gettext(\"Default Password\") %>\n            <%= password_input f, :current_password, class: \"form-control\", required: true %>\n            <%= error_tag f, :current_password %>\n          </div>\n\n          <div class=\"form-group\">\n            <%= label f, :password, gettext(\"New Password\") %>\n            <%= password_input f, :password, class: \"form-control\", required: true %>\n            <%= error_tag f, :password %>\n          </div>\n\n          <div class=\"form-group\">\n            <%= label f, :password_confirmation, gettext(\"Confirmation Password\") %>\n            <%= password_input f, :password_confirmation, class: \"form-control\", required: true %>\n            <%= error_tag f, :password_confirmation %>\n          </div>\n\n          <div class=\"form-group text-center\">\n            <%= submit gettext(\"Update Password\"), class: \"btn btn-primary\" %>\n          </div>\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/setting/profile.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-sm-2 col-md-offset-1 setting-menu\">\n    <%= render \"_sidebar.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-10\">\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <%= form_for @changeset, setting_path(@conn, :update), fn f -> %>\n          <%= hidden_input f, :by, value: \"profile\" %>\n\n          <div class=\"form-group\">\n            <%= label f, :location_id, gettext(\"Location\") %>\n            <%= select f, :location_id, location_option(), class: \"form-control\", style: \"width: 400px\" %>\n          </div>\n\n          <div class=\"form-group\">\n            <label for=\"user_github\">GitHub</label>\n            <div class=\"input-group\">\n              <span class=\"input-group-prepend\">\n                <span class=\"input-group-text\">https://github.com/</span>\n              </span>\n              <%= text_input f, :github_handle, class: \"form-control\" %>\n            </div>\n          </div>\n\n          <div class=\"form-group\">\n            <%= label f, :website, gettext(\"Website\") %>\n            <%= text_input f, :website, class: \"form-control\", placeholder: \"http://\" %>\n          </div>\n\n          <div class=\"form-group\">\n            <%= label f, :tagline, gettext(\"Tagline\") %>\n            <%= text_input f, :tagline, class: \"form-control\" %>\n          </div>\n\n          <div class=\"form-actions hide-ios\">\n            <%= submit gettext(\"Update\"), class: \"btn btn-primary\" %>\n          </div>\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/setting/reward.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-sm-2 col-md-offset-1 setting-menu\">\n    <%= render \"_sidebar.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-10\">\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <div class=\"row\">\n          <div class=\"col-md-8\">\n            <%= form_for @changeset, setting_path(@conn, :update), [multipart: true], fn f -> %>\n              <%= hidden_input f, :by, value: \"reward\" %>\n\n              <div class=\"form-group form-group-reward-field-alipay\">\n                <%= label f, :alipay, \"支付宝\" %>\n                <%= file_input f, :alipay, class: \"form-control\" %>\n                <p class=\"form-text\">\n                  打开支付宝客户端：右上角 “+” -> 我要收款 -> 设定金额 -> 保存二维码图片\n                </p>\n              </div>\n\n              <div class=\"form-group form-group-reward-field-wechat\">\n                <%= label f, :wechat, \"微信\" %>\n                <%= file_input f, :wechat, class: \"form-control\" %>\n                <p class=\"form-text\">\n                  打开微信手机客户端：右上角 “+” -> 收付款 -> 我要收款 -> 保存二维码图片\n                </p>\n              </div>\n\n              <div class=\"form-actions hide-ios\">\n                <%= submit \"更新资料\", class: \"btn btn-primary\" %>\n              </div>\n            <% end %>\n          </div>\n          <div class=\"col-md-4\">\n            <div class=\"markdown\">\n              <h3>关于打赏</h3>\n              <p>打赏功能为了鼓励分享而设计。</p>\n              <p>上传你的微信、支付宝\n                <strong>收款二维码</strong>，之后打赏按钮将会出现在 “个人主页” 和 “文章末尾” 显示。\n              </p>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/setting/show.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-sm-2 col-md-offset-1 setting-menu\">\n    <%= render \"_sidebar.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-10\">\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <%= form_for @changeset, setting_path(@conn, :update), [multipart: true], fn f -> %>\n          <%= hidden_input f, :by, value: \"show\" %>\n          <div class=\"row\">\n            <div class=\"col-sm-9\">\n              <div class=\"form-group\">\n                <%= text_input f, :email, class: \"form-control\", disabled: true %>\n              </div>\n\n              <div class=\"form-group\">\n                <%= checkbox(f, :email_public) %> <%= gettext \"Public Email\" %>\n\n              </div>\n\n              <div class=\"form-group\">\n                <%= file_input f, :avatar %>\n                <div class=\"form-text\">\n                <%= gettext \"Upload avatar\" %>\n                </div>\n              </div>\n\n              <div class=\"form-group\">\n                <%= textarea f, :bio, class: \"form-control\", placeholder: \"Add user bio\", rows: 4 %>\n                <%= error_tag f, :bio %>\n              </div>\n            </div>\n            <div class=\"col-sm-3\">\n              <div class=\"form-group avatar-preview\" style=\"text-align: center;\">\n                <img class=\"media-object avatar-96\" src=\"<%= qn_url(@current_user.avatar) %>\" />\n              </div>\n            </div>\n          </div>\n          <div class=\"form-actions hide-ios\">\n            <%= submit gettext(\"Update\"), class: \"btn btn-primary\" %>\n          </div>\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/shared/_pagination.html.eex",
    "content": "<%= if @paginate.total_pages > 1 do %>\n  <div class=\"pagination\">\n    <%= turbo_pagination_links(@conn, @paginate) %>\n  </div>\n<% end %>\n"
  },
  {
    "path": "lib/mipha_web/templates/team/_nav.html.eex",
    "content": "<ul class=\"nav nav-tabs team-menu mi-nav\">\n  <li class=\"nav-item\">\n    <%= link gettext(\"Team Topics\"), to: team_path(@conn, :show, @team), class: \"nav-link\" %>\n  </li>\n  <li class=\"nav-item\">\n    <%= link gettext(\"Team People\"), to: team_path(@conn, :people, @team), class: \"nav-link\" %>\n  </li>\n</ul>"
  },
  {
    "path": "lib/mipha_web/templates/team/index.html.eex",
    "content": "<h1>Team Index Page</h1>"
  },
  {
    "path": "lib/mipha_web/templates/team/people.html.eex",
    "content": "<%= render \"_nav.html\", assigns %>\n\n<div class=\"card team-users\">\n  <div class=\"card-body\">\n    <table class=\"table table-striped\">\n      <tbody>\n        <%= for item <- @team.users do %>\n          <tr class=\"team-user clearfix\">\n            <td class=\"avatar\">\n              <img class=\"media-object avatar-32\" src=\"<%= qn_url(item.avatar) %>\">\n            </td>\n            <td class=\"name\">\n              <%= link item.username, to: user_path(@conn, :show, item.username), class: \"user-name\" %>\n            </td>\n            <td class=\"role\">\n              <% is_owner?(@team, item) && \"组管理员\" %>\n            </td>\n          </tr>\n        <% end %>\n      </tbody>\n    </table>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/team/show.html.eex",
    "content": "<%= render \"_nav.html\", assigns %>\n\n<div class=\"row\">\n  <div class=\"col-md-8\">\n    <div class=\"card topics\">\n      <div class=\"card-body\">\n        <%= for item <- @topics do %>\n          <%= render MiphaWeb.TopicView, \"_topic.html\", assigns: assigns, item: item %>\n        <% end %>\n      </div>\n      <div class=\"card-footer\">\n        <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"sidebar team-sidebar col-sm-4\">\n    <div class=\"card members\">\n      <div class=\"card-header\">成员列表</div>\n      <div class=\"card-body\">\n        <%= for item <- @team.users do %>\n          <%= link to: user_path(@conn, :show, item.username), title: item.username do %>\n            <img class=\"media-object avatar-32\" src=\"<%= qn_url(item.avatar) %>\">\n          <% end %>\n        <% end %>\n      </div>\n      <div class=\"card-footer\">\n        <%= link \"所有成员...\", to: team_path(@conn, :people, @team) %>\n      </div>\n    </div>\n\n    <%= render MiphaWeb.UserView, \"_repos.html\", object: @team %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_editor_toolbar.html.eex",
    "content": "<div class=\"editor-toolbar clearfix\">\n  <div class=\"reply-to pull-right\" style=\"display:none;\">\n    <i class=\"fa fa-mail-reply\"></i>\n    <a href=\"#\" class=\"user\"></a>\n    <a href=\"#\" class=\"close\">\n      <i class=\"fa fa-close\"></i>\n    </a>\n  </div>\n  <div class=\"opts\">\n    <%# <a href=\"#\" class=\"nav-link pickup-emoji\">\n      <i class=\"fa fa-smile-o\"></i>\n    </a> %>\n    <span class=\"dropdown\" id=\"dropdown-insert-codes\">\n      <a href=\"#editor-toolbar-insert-code\" class=\"nav-link dropdown-toggle\" data-toggle=\"dropdown\" title=\"插入代码\">\n        <i class=\"fa fa-code\"></i>\n      </a>\n      <div class=\"dropdown-menu insert-codes\" aria-labelledby=\"dropdown-insert-codes\">\n        <a class=\"dropdown-item\" data-lang=\"ruby\" href=\"#\">Ruby</a>\n        <a class=\"dropdown-item\" data-lang=\"elixir\" href=\"#\">Elixir / Erlang</a>\n        <a class=\"dropdown-item\" data-lang=\"erb\" href=\"#\">HTML / ERB</a>\n        <a class=\"dropdown-item\" data-lang=\"scss\" href=\"#\">CSS / SCSS</a>\n        <a class=\"dropdown-item\" data-lang=\"shell\" href=\"#\">Shell / Bash</a>\n        <a class=\"dropdown-item\" data-lang=\"js\" href=\"#\">JavaScript</a>\n        <a class=\"dropdown-item\" data-lang=\"yml\" href=\"#\">YAML</a>\n        <a class=\"dropdown-item\" data-lang=\"python\" href=\"#\">Python</a>\n        <a class=\"dropdown-item\" data-lang=\"java\" href=\"#\">Java</a>\n        <a class=\"dropdown-item\" data-lang=\"php\" href=\"#\">PHP</a>\n      </div>\n    </span>\n    <a id=\"editor-upload-image\" title=\"上传图片\" class=\"nav-link\" href=\"#\">\n      <i class='fa fa-image'></i>\n    </a>\n    <a href=\"#\" class=\"nav-link preview\">\n      <%= gettext \"Preview\" %>\n    </a>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_form.html.eex",
    "content": "<div class=\"card-body\">\n  <%= render \"_node_selector.html\", assigns %>\n  <%= form_for @changeset, @action, fn f -> %>\n    <input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" />\n    <%= hidden_input f, :node_id %>\n    <%= error_tag f, :node_id %>\n    <div class=\"form-group\">\n      <div class=\"input-group\">\n        <div class=\"input-group-prepend\">\n          <button type=\"button\" class=\"btn btn-default\" id=\"node-selector-button\" data-toggle=\"modal\" data-target=\"#node-selector\">\n            <%= Map.get(assigns, :topic) && @topic.node.name || \"选择节点...\" %>\n          </button>\n        </div>\n        <%= text_input f, :title, class: \"form-control\", required: true, placeholder: \"Title\" %>\n        <%= error_tag f, :title %>\n      </div>\n    </div>\n    <%= render \"_editor_toolbar.html\", assigns %>\n\n    <div class=\"form-group\">\n      <%= textarea f, :body, class: \"form-control topic-editor closewarning\", required: true, placeholder: \"Body\", rows: 20 %>\n      <%= error_tag f, :body %>\n    </div>\n    <div class=\"form-actions hide-ios\">\n      <%= submit gettext(\"Submit\"), class: \"btn btn-primary\" %>\n      <div class=\"pull-right hide-ios\">\n        <a href=\"/markdown\" target=\"_blank\"><i class=\"fa fa-tip\"></i>\n          <%= gettext \"Markdown Guide\" %>\n        </a>\n      </div>\n    </div>\n  <% end %>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_handle_reply.html.eex",
    "content": "<%= if @current_user do %>\n  <%= if is_nil(@topic.closed_at) do %>\n    <div id=\"reply\" class=\"card\">\n      <div class=\"card-header hide-ios\">\n        <%= gettext \"Reply\" %>\n      </div>\n      <div class=\"card-body\">\n        <form id=\"new_reply\" action=\"/topics/<%= @topic.id %>/replies\" accept-charset=\"UTF-8\" method=\"post\">\n          <%= render \"_editor_toolbar.html\", assigns %>\n          <input type=\"hidden\" name=\"_method\" value=\"post\" />\n          <input type=\"hidden\" name=\"_csrf_token\" value=\"<%= get_csrf_token() %>\" />\n          <input type=\"hidden\" name=\"reply[parent_id]\" id=\"reply_parent_id\" />\n          <input type=\"hidden\" name=\"reply[topic_id]\" id=\"reply_topic_id\" value=\"<%= @topic.id %>\" />\n          <div class=\"form-group\">\n            <textarea class=\"topic-editor form-control\" rows=\"4\" tabindex=\"1\" name=\"reply[content]\" id=\"reply_content\"></textarea>\n          </div>\n          <div class=\"submit-buttons\">\n            <button type=\"submit\" id=\"reply-button\" class=\"btn btn-primary\">\n              <%= gettext \"Submit\" %>\n            </button>\n            <span class=\"help-inline\" style=\"padding-left: 5px;\" title=\"或者 Ctrl + Enter\">\n              <kbd>Command</kbd> +\n              <kbd>Enter</kbd>\n            </span>\n            <div class=\"pull-right\">\n              <a href=\"/markdown\" target=\"_blank\">\n                <%= gettext \"Markdown Guide\" %>\n              </a>\n            </div>\n          </div>\n        </form>\n      </div>\n    </div>\n  <% end %>\n<% else %>\n  <%= render \"_need_register_or_login.html\", assigns %>\n<% end %>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_need_register_or_login.html.eex",
    "content": "<div class=\"card\">\n  <div class=\"card-body\">\n    <div id=\"reply\" class=\"form box\">\n      <div style=\"padding:20px;\" data-turbolinks-action=\"replace\">\n        You need <a class=\"btn btn-primary btn-sm\" href=\"/login\">Login</a>\n        to reply, Or\n        <a class=\"btn btn-danger btn-sm\" href=\"/join\">Register</a>。\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_node_selector.html.eex",
    "content": "<div class=\"modal\" id=\"node-selector\" tabindex=\"-1\" role=\"dialog\" style=\"display: none;\" aria-hidden=\"true\">\n  <div class=\"modal-dialog modal-lg\" role=\"document\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <div class=\"modal-title\"><%= gettext \"Nodes\" %></div>\n        <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">\n          <span aria-hidden=\"true\">×</span>\n        </button>\n      </div>\n      <div class=\"modal-body\">\n        <div class=\"index-sections card\">\n          <div class=\"card-header\"><%= gettext \"Nodes\" %></div>\n          <div class=\"card-body\">\n            <div class=\"row node-list\">\n              <%= for parent_node <- @parent_nodes do %>\n                <div class=\"node media\">\n                  <label class=\"mr-2 media-left\"><%= parent_node.name %></label>\n                  <span class=\"nodes media-body\">\n                    <%= for item <- parent_node.children do %>\n                      <span class=\"name\">\n                        <a data-id=\"<%= item.id %>\" href=\"/topics/?node_id=<%= item.id %>\"><%= item.name %></a>\n                      </span>\n                    <% end %>\n                  </span>\n                </div>\n              <% end %>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_nodes.html.eex",
    "content": "<div class=\"index-sections card\">\n  <div class=\"card-header\"><%= gettext \"Nodes\" %></div>\n  <div class=\"card-body\">\n    <div class=\"row node-list\">\n      <%= for parent <- @parent_nodes do %>\n        <div class=\"node media\">\n          <label class=\"mr-2 media-left\"><%= parent.name %></label>\n          <span class=\"nodes media-body\">\n            <%= for item <- parent.children do %>\n              <span class=\"name\">\n                <a href=\"/topics?node_id=<%= item.id %>\"><%= item.name %></a>\n              </span>\n            <% end %>\n          </span>\n        </div>\n      <% end %>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_operate_toolbar.html.eex",
    "content": "<div class=\"opts\">\n  <%= if has_starred?(user: @current_user, topic: @topic) do %>\n    <%= link to: topic_path(@conn, :unstar, @topic), method: :post, class: \"likeable active\" do %>\n      <i class=\"fa fa-heart\"></i>\n      <span><%= gettext \"%{star_count} stars\", star_count: @topic.star_count %></span>\n    <% end %>\n  <% else %>\n    <%= link to: topic_path(@conn, :star, @topic), method: :post, class: \"likeable deactive\" do %>\n      <i class=\"fa fa-heart\"></i>\n      <span><%= gettext \"%{star_count} stars\", star_count: @topic.star_count %></span>\n    <% end %>\n  <% end %>\n\n  <%= if has_collected?(@current_user, @topic) do %>\n    <%= link to: topic_path(@conn, :uncollection, @topic), method: :post, class: \"bookmark active\" do %>\n      <i class=\"fa fa-bookmark\"></i> <%= gettext \"Uncollection\" %>\n    <% end %>\n  <% else %>\n    <%= link to: topic_path(@conn, :collection, @topic), method: :post, class: \"bookmark\" do %>\n      <i class=\"fa fa-bookmark\"></i> <%= gettext \"Collection\" %>\n    <% end %>\n  <% end %>\n\n  <%# 只有管理员可以设置「置顶」或「加精」 %>\n  <%= if @current_user && @current_user.is_admin do %>\n    <%= if @topic.suggested_at do %>\n      <%= link to: topic_path(@conn, :unsuggest, @topic), method: :post do %>\n        <i class=\"fa fa-angle-double-down\"></i> <%= gettext \"Unpin\" %>\n      <% end %>\n    <% else %>\n      <%= link to: topic_path(@conn, :suggest, @topic), method: :post do %>\n        <i class=\"fa fa-angle-double-up\"></i> <%= gettext \"Pin\" %>\n      <% end %>\n    <% end %>\n\n    <%= if @topic.type == :featured do %>\n      <%= link to: topic_path(@conn, :normal, @topic), method: :post, title: \"Unmark featured\" do %>\n        <i class=\"fa fa-diamond\"></i> <%= gettext \"Unmark featured\" %>\n      <% end %>\n    <% else %>\n      <%= link to: topic_path(@conn, :excellent, @topic), method: :post, title: \"Mark featured\" do %>\n        <i class=\"fa fa-diamond\"></i> <%= gettext \"Mark featured\" %>\n      <% end %>\n    <% end %>\n  <% end %>\n\n  <%# FIXME 后续考虑权限控制 %>\n  <%= if @current_user && @current_user.id == @topic.user_id do %>\n    <span class=\"pull-right opts\">\n      <%= if @topic.closed_at do %>\n        <%= link \"\", to: topic_path(@conn, :open, @topic), method: :post, title: \"Reopen\", class: \"fa fa-undo\" %>\n      <% else %>\n        <%= link \"\", to: topic_path(@conn, :close, @topic), method: :post, title: \"Closed\", class: \"fa fa-check\" %>\n      <% end %>\n      <%= link \"\", to: topic_path(@conn, :edit, @topic), title: \"Change Topic\", class: \"fa fa-pencil\" %>\n      <%= link \"\", to: topic_path(@conn, :delete, @topic), title: \"Delete Topic\", class: \"fa fa-trash\", method: :delete, data: [confirm: \"Are you sure?\"] %>\n    </span>\n  <% end %>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_relation_topic.html.eex",
    "content": "<div class=\"card\">\n  <div class=\"card-header\">相关话题</div>\n  <ul class=\"list-group list-group-flush\">\n    <li class=\"list-group-item\"><a href=\"/topics/1040\">谈谈今天 ruby-china 的优化</a></li>\n    <li class=\"list-group-item\"><a href=\"/topics/33254\">Vue.js 项目重构，轻松实现上拉加载滚动位置还原</a></li>\n    <li class=\"list-group-item\"><a href=\"/topics/24996\">Rails 3 和 Rails 4 中 ETags 工作原理</a></li>\n    <li class=\"list-group-item\"><a href=\"/topics/95\">缓存问题：话题相关页面 可能 半个小时内没有热门节点</a></li>\n    <li class=\"list-group-item\"><a href=\"/topics/7252\">count_cache 与 XX_count</a></li>\n  </ul>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/topic/_replies.html.eex",
    "content": "<div id=\"replies\" class=\"card\">\n  <div class=\"total card-header hide-ios\">\n    <%= gettext \"Total\" %>\n    <b><%= @topic.reply_count %></b> <%= gettext \"replies\" %>\n  </div>\n  <div class=\"items card-body\">\n    <%= for {item, idx} <- Enum.with_index(@topic.replies) do %>\n      <div class=\"reply <%= popular(item) %>\" data-id=\"<%= item.id %>\" id=\"reply<%= item.id %>\">\n        <div id=\"reply-<%= item.id %>\" data-floor=\"<%= idx + 1 %>\">\n          <div class=\"avatar\">\n            <%= link to: user_path(@conn, :show, item.user.username), title: item.user.username do %>\n              <img class=\"media-object avatar-48\" src=\"<%= qn_url(item.user.avatar) %>\" />\n            <% end %>\n          </div>\n          <div class=\"infos\">\n            <div class=\"info\">\n              <span class=\"name\">\n                <%= link item.user.username, to: user_path(@conn, :show, item.user.username), class: \"user-name\" %>\n              </span>\n              <span class=\"sub-info\">\n                <span class='floor'>#<%= idx + 1 %></span> ·\n                <%= time_tag(item.updated_at) %>\n              </span>\n              <span class=\"opts pull-right\">\n                <span class=\"hideable\">\n                  <%= link \"\", to: topic_reply_path(@conn, :edit, @topic, item), title: gettext(\"Edit reply\"), class: \"fa fa-pencil\" %>\n                  <a data-id=\"<%= item.id %>\" data-login=\"<%= item.user.username %>\" title=\"\" class=\"btn-reply fa fa-mail-reply\" href=\"#\"></a>\n                </span>\n                <%= if has_starred?(user: @current_user, reply: item) do %>\n                  <%= link to: topic_reply_reply_path(@conn, :unstar, @topic, item), method: :post, title: \"\", class: \"likeable active\" do %>\n                    <i class=\"fa fa-heart\"></i>\n                    <span><%= item.star_count %> 个赞</span>\n                  <% end %>\n                <% else %>\n                  <%= link to: topic_reply_reply_path(@conn, :star, @topic, item), method: :post, title: \"\", class: \"likeable deactive\" do %>\n                    <i class=\"fa fa-heart\"></i>\n                    <span><%= gettext \"%{star_count} stars\", star_count: item.star_count %></span>\n                  <% end %>\n                <% end %>\n              </span>\n            </div>\n            <%= if item.parent_id do %>\n              <div class=\"reply-to-block\">\n                <div class=\"info\">\n                  <%= gettext \"Replied to\" %>\n                  <a>\n                    <img class=\"media-object avatar-16\" src=\"<%= qn_url(item.parent.user.avatar) %>\">\n                    <span class=\"user-name\"><%= item.parent.user.username %></span>\n                  </a>\n                </div>\n              </div>\n            <% end %>\n            <div class=\"markdown\">\n              <%= markdown(item.content) %>\n            </div>\n          </div>\n        </div>\n      </div>\n    <% end %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_right.html.eex",
    "content": "<div class=\"card\">\n  <div class=\"card-body\">\n    <a class=\"btn btn-primary btn-block\" href=\"/topics/new\"><%= gettext \"New Topic\" %></a>\n  </div>\n</div>\n<div class=\"card\">\n  <div class=\"card-header\"><%= gettext \"Note\" %></div>\n  <div class=\"card-body\">\n    <%= gettext \"Mipha is open source elixir forum, for more information, please visit\" %>\n    <a href=\"https://github.com/zven21/mipha\" target=\"_blank\">Mipha</a>\n  </div>\n</div>\n<div class=\"card\">\n  <div class=\"card-header\"><%= gettext \"Friendship community\" %></div>\n  <ul class=\"list-group list-group-flush\" style=\"text-align: center;\">\n    <li class=\"list-group-item\">\n      <a href=\"http://cnodejs.org\" rel=\"nofollow\" title=\"CNode 社区\" target=\"_blank\">\n        <img src=\"//l.ruby-china.com/photo/2016/d427ef3efd33b57721df152c2aa1735e.png\" style=\"width:130px; \">\n      </a>\n    </li>\n    <li class=\"list-group-item\">\n      <a href=\"https://gocn.io\" rel=\"nofollow\" title=\"GoCN 社区\" target=\"_blank\">\n        <img src=\"//l.ruby-china.com/photo/2017/97b87be9-1375-40cb-8d56-29e203a31689.png\" style=\"width:130px; \">\n      </a>\n    </li>\n    <li class=\"list-group-item\">\n      <a href=\"https://laravel-china.org\" target=\"_blank\" rel=\"nofollow\">\n        <img src=\"//l.ruby-china.com/photo/2016/d7782871f3fac7e85a95d20c74046909.png\" style=\"width:130px; \">\n      </a>\n    </li>\n    <li class=\"list-group-item\">\n      <a href=\"https://testerhome.com/\" target=\"_blank\" rel=\"nofollow\">\n        <img src=\"//l.ruby-china.com/photo/2016/5cd78b730062ab3c768bcc2592c5c7fa.png\" style=\"width: 150px;\">\n      </a>\n    </li>\n    <li class=\"list-group-item\">\n      <a href=\"https://pythoncaff.com\" target=\"_blank\" rel=\"nofollow\">\n        <img src=\"//l.ruby-china.com/photo/2018/00d4dd0a-188b-4c58-a524-46c99244659b.png\" style=\"width: 150px;\">\n      </a>\n    </li>\n  </ul>\n</div>\n\n<div class=\"card\">\n  <div class=\"card-header\"><%= gettext \"Statistics\" %></div>\n  <ul class=\"list-group list-group-flush\">\n    <li class=\"list-group-item\"><%= gettext \"Users Count: %{user_count}\", user_count: get_user_count() %></li>\n    <li class=\"list-group-item\"><%= gettext \"Topics Count: %{topic_count}\", topic_count: get_topic_count() %></li>\n    <li class=\"list-group-item\"><%= gettext \"Replies Count: %{reply_count}\", reply_count: get_reply_count() %></li>\n  </ul>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_right_sidebar.html.eex",
    "content": "<div id=\"topic-sidebar\" data-spy=\"affix\" data-offset-bottom=\"65\">\n  <div class=\"card\">\n    <div class=\"card-body\">\n      <div class=\"card-body\">\n        <a href=\"#\" class=\"btn btn-default btn-block btn-sm btn-move-page\" data-type=\"top\">\n          <i class=\"fa fa-arrow-up\"></i>\n        </a>\n        <div class=\"buttons\">\n          <div class=\"group likes opts\">\n            <%= if has_starred?(user: @current_user, topic: @topic) do %>\n              <%= link to: topic_path(@conn, :unstar, @topic), method: :post, class: \"likeable active\" do %>\n                <i class=\"fa fa-heart\"></i>\n              <% end %>\n            <% else %>\n              <%= link to: topic_path(@conn, :star, @topic), method: :post, class: \"likeable deactive\" do %>\n                <i class=\"fa fa-heart\"></i>\n              <% end %>\n            <% end %>\n          </div>\n          <div class=\"group\">\n            <div class=\"btn-group\" role=\"group\">\n              <%= if has_collected?(@current_user, @topic) do %>\n                <%= link to: topic_path(@conn, :uncollection, @topic), method: :post, class: \"bookmark active btn btn-default\" do %>\n                  <i class=\"fa fa-bookmark\"></i> <%= gettext \"Uncollection\" %>\n                <% end %>\n              <% else %>\n                <%= link to: topic_path(@conn, :collection, @topic), method: :post, class: \"bookmark btn btn-default\" do %>\n                  <i class=\"fa fa-bookmark\"></i> <%= gettext \"Collection\" %>\n                <% end %>\n              <% end %>\n            </div>\n          </div>\n        </div>\n        <hr>\n        <div class=\"reply-buttons\">\n          <div class=\"total\">\n            <%= gettext \"Total %{topic_count} replies\", topic_count: @topic.reply_count %>\n          </div>\n        </div>\n        <hr>\n        <a href=\"#\" class=\"btn btn-default btn-sm btn-block btn-move-page\" data-type=\"bottom\">\n          <i class=\"fa fa-arrow-down\"></i>\n        </a>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"notify-updated\">\n    <a class=\"update\" href=\"#\">\n      <i class=\"fa fa-bell-o\"></i>\n      <b>有新回复！</b>点击这里立即载入</a>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_topic.html.eex",
    "content": "<div class=\"topic media\">\n  <div class=\"avatar\">\n    <%= link to: user_path(MiphaWeb.Endpoint, :show, @item.user.username), title: @item.user.username do %>\n      <img class=\"media-object avatar-48\" src=\"<%= qn_url(@item.user.avatar) %>\">\n    <% end %>\n  </div>\n  <div class=\"infos media-body\">\n    <div class=\"title media-heading\">\n      <%= link to: topic_path(MiphaWeb.Endpoint, :show, @item), title: @item.title do %>\n        <span class=\"node\"><%= @item.node && @item.node.name %></span>\n        <span><%= @item.title %></span>\n      <% end %>\n      <%= if @item.type == :featured do %>\n        <i title=\"Featured\" class=\"fa fa-diamond\"></i>\n      <% end %>\n      <%= if @item.suggested_at do %>\n        <i class=\"fa fa-angle-double-up\" title=\"Suggested\"></i>\n      <% end %>\n    </div>\n    <div class=\"info\">\n      <a class=\"user-name\" href=\"/u/<%= @item.user.username %>\"><%= @item.user.username %></a>\n      <%= if @item.reply_count != 0 do %>\n        <span class=\"hidden-mobile\">\n          • <%= gettext \"Last by\" %>\n          <a class=\"user-name\" href=\"/u/<%= @item.last_reply_user.username %>\">\n            <%= @item.last_reply_user && @item.last_reply_user.username %>\n          </a>\n          <%= gettext \"replied at\" %> <%= time_tag(@item.updated_at) %>\n        </span>\n      <% else %>\n        • <%= gettext \"Created at\" %> <%= time_tag(@item.updated_at) %>\n      <% end %>\n    </div>\n  </div>\n  <div class=\"count media-right\">\n    <a class=\"state-false\" href=\"/topics/<%= @item.id %>#reply<%= @item.last_reply_id %>\"><%= @item.reply_count %></a>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/_topics.html.eex",
    "content": "<div class=\"topics topics-index card\">\n  <div class=\"card-body item-list\">\n    <%= for item <- @topics do %>\n      <%= render \"_topic.html\", assigns: assigns, item: item %>\n    <% end %>\n  </div>\n\n  <div class=\"card-footer clearfix\">\n    <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/edit.html.eex",
    "content": "<div class=\"card\">\n  <div class=\"card-header hide-ios\">\n    <%= gettext \"Edit Topic\" %>\n  </div>\n  <%= render \"_form.html\", Map.put(assigns, :action, topic_path(@conn, :update, @topic)) %>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/topic/educational.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-md-9\">\n    <%= render \"_topics.html\", assigns %>\n    <%= render \"_nodes.html\", assigns %>\n  </div>\n\n  <div class=\"sidebar col-md-3\">\n    <%= render \"_right.html\", assigns %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/featured.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-md-9\">\n    <%= render \"_topics.html\", assigns %>\n    <%= render \"_nodes.html\", assigns %>\n  </div>\n\n  <div class=\"sidebar col-md-3\">\n    <%= render \"_right.html\", assigns %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/index.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-md-9\">\n    <%= render \"_topics.html\", assigns %>\n    <%= render \"_nodes.html\", assigns %>\n  </div>\n\n  <div class=\"sidebar col-md-3\">\n    <%= render \"_right.html\", assigns %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/jobs.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-md-9\">\n    <%= render \"_topics.html\", assigns %>\n    <%= render \"_nodes.html\", assigns %>\n  </div>\n\n  <div class=\"sidebar col-md-3\">\n    <%= render \"_right.html\", assigns %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/new.html.eex",
    "content": "<div class=\"card\">\n  <div class=\"card-header hide-ios\">\n    <%= gettext \"New Topic\" %>\n  </div>\n  <%= render \"_form.html\", Map.put(assigns, :action, topic_path(@conn, :create)) %>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/topic/no_reply.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-md-9\">\n    <%= render \"_topics.html\", assigns %>\n    <%= render \"_nodes.html\", assigns %>\n  </div>\n\n  <div class=\"sidebar col-md-3\">\n    <%= render \"_right.html\", assigns %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/popular.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col-md-9\">\n    <%= render \"_topics.html\", assigns %>\n    <%= render \"_nodes.html\", assigns %>\n  </div>\n\n  <div class=\"sidebar col-md-3\">\n    <%= render \"_right.html\", assigns %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/topic/show.html.eex",
    "content": "<script>window.channelTopicId = \"<%= @topic.id %>\"</script>\n<div class=\"row\">\n  <div class=\"col-md-9\">\n    <div class=\"topic-detail card\">\n      <div class=\"card-header media clearfix\">\n        <div class=\"media-body\">\n          <h1 class=\"media-heading\">\n            <a class=\"node\" href=\"/topics?node_id=<%= @topic.node_id %>\">\n              <%= @topic.node.name %>\n            </a>\n            <span class=\"title\"><%= @topic.title %></span>\n          </h1>\n          <div class=\"info\">\n            <%= link @topic.user.username, to: user_path(@conn, :show, @topic.user.username) %>\n            <%= time_tag(@topic.inserted_at) %>\n            <%= if @topic.reply_count != 0 do %>\n              <span class=\"hidden-mobile\">\n                • <%= gettext \"Last by\" %>\n                <%= link @topic.last_reply_user.username, to: user_path(@conn, :show, @topic.last_reply_user.username), class: \"user-name\" %>\n                <%= gettext \"replied at\" %> <%= time_tag(@topic.updated_at) %>\n                <%= time_tag(@topic.updated_at) %>\n              </span>\n            <% end %>\n            ·\n            <%= @topic.visit_count %> <%= gettext \"hits\" %>\n          </div>\n        </div>\n        <div class=\"avatar media-right\">\n          <%= link to: user_path(@conn, :show, @topic.user.username), title: @topic.user.username do %>\n            <img class=\"media-object avatar-48\" src=\"<%= qn_url(@topic.user.avatar) %>\" />\n          <% end %>\n        </div>\n      </div>\n\n      <%= if @topic.type == :featured do %>\n        <div class=\"label-awesome\">\n          <i class=\"fa fa-diamond awesome\"></i>\n          本帖已被设为精华帖！\n        </div>\n      <% end %>\n\n      <div class=\"card-body markdown markdown-toc\">\n        <%= markdown(@topic.body) %>\n      </div>\n\n      <div class=\"card-footer clearfix\">\n        <%= render \"_operate_toolbar.html\", assigns %>\n      </div>\n    </div>\n\n    <%= render \"_replies.html\", assigns %>\n    <%= render \"_handle_reply.html\", assigns %>\n    <%#= render \"_relation_topic.html\", assigns %>\n  </div>\n\n  <div class=\"sidebar hidden-mobile col-md-3\">\n    <%= render \"_right_sidebar.html\", assigns %>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/user/_left.html.eex",
    "content": "<div class=\"card profile\">\n  <div class=\"card-body\">\n    <div class=\"media\">\n      <div class=\"avatar media-left mr-3\">\n        <div class='image'><img class=\"media-object avatar-96\" src=\"<%= qn_url(@user.avatar) %>\" /></div>\n        <div class=\"level\"><%= user_level_tag(@user) %></div>\n      </div>\n      <div class=\"media-body\">\n        <div class=\"item\">\n          <%= @user.username %>\n          <span class=\"opts pull-right\">\n        </span>\n        </div>\n        <div class=\"item number\">\n          第 <%= @user.id %> 位会员 / <span title=\"\"><%= format_inserted_date(@user.inserted_at) %></span>\n        </div>\n        <div class=\"item company\">\n          <%= if @user.company do %>\n            <span><%= link @user.company.name, to: company_path(@conn, :show, @user.company) %></span>\n          <% end %>\n          <% if @user.location do %>\n             <span>\n               <%= link @user.location.name, to: location_path(@conn, :show, @user.location) %>\n             </span>\n          <% end %>\n        </div>\n        <div class=\"item counts\">\n          <span><%= user_topics_count(@user) %></span> topics • <span><%= user_replies_count(@user) %></span> replies\n        </div>\n        <div class=\"item social\">\n          <%= if @user.website do %>\n            <a target=\"_blank\" rel=\"nofollow\" href=\"<%= @user.website %>\"><i class=\"fa fa-globe\"></i></a>\n          <% end %>\n          <%= if @user.github_handle do %>\n            <a target=\"_blank\" rel=\"nofollow\" href=\"//github.com/<%= @user.github_handle %>\"><i class=\"fa fa-github\"></i></a>\n          <% end %>\n          <%= if @user.email_public do %>\n            <a href=\"mailto: <%= @user.email %>\"><i class=\"fa fa-envelope-o\"></i></a>\n          <% end %>\n        </div>\n      </div>\n    </div>\n    <div class=\"follow-info row hide-ios\">\n      <div class=\"col-sm-4 followers\" data-login=\"huacnlee\">\n        <%= link user_followers_count(@user), to: user_followers_path(@conn, :followers, @user.username), class: \"counter\" %>\n        <%= link gettext(\"Follower\"), to: user_followers_path(@conn, :followers, @user), class: \"text\" %>\n      </div>\n      <div class=\"col-sm-4 following\">\n        <%= link user_following_count(@user), to: user_following_path(@conn, :following, @user.username), class: \"counter\" %>\n        <%= link gettext(\"Following\"), to: user_following_path(@conn, :following, @user), class: \"text\" %>\n      </div>\n      <div class=\"col-sm-4 stars\">\n        <%= link user_collections_count(@user), to: user_collections_path(@conn, :collections, @user.username), class: \"counter\" %>\n        <%= link gettext(\"Collection\"), to: user_collections_path(@conn, :collections, @user), class: \"text\" %>\n      </div>\n    </div>\n    <div class=\"buttons row\">\n      <%= if @current_user && @current_user.id != @user.id do %>\n      <div class=\"col-sm-12\">\n        <%= if has_followed?(@current_user, @user) do %>\n          <%= link to: user_unfollow_path(@conn, :unfollow, @user.username), method: :post, class: \"button-follow-user btn btn-default btn-block\" do %>\n            <i class=\"fa fa-user\"></i>\n            <span><%= gettext \"UnFollow\" %></span>\n          <% end %>\n        <% else %>\n          <%= link to: user_follow_path(@conn, :follow, @user.username), method: :post, class: \"button-follow-user btn btn-primary btn-block\" do %>\n            <i class=\"fa fa-user\"></i>\n            <span><%= gettext \"Follow\" %></span>\n          <% end %>\n        <% end %>\n      </div>\n      <% end %>\n    </div>\n\n    <div class=\"tagline row\">\n      <%= @user.bio %>\n    </div>\n\n    <div class=\"buttons\">\n      <button type=\"button\" class=\"btn-block btn btn-default\" id=\"node-selector-button\" data-toggle=\"modal\" data-target=\"#reward-modal\">\n        <i class='fa fa-qrcode'></i> <span><%= gettext \"Reward\" %></span>\n      </button>\n    </div>\n  </div>\n</div>\n\n<%= render \"_teams.html\", assigns %>\n<%= render \"_repos.html\", object: @user %>\n<%= render \"_reward.html\", assigns %>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/_menu.html.eex",
    "content": "<ul class=\"nav nav-tabs mi-nav\">\n  <li class=\"nav-item\"><%= link gettext(\"Info\"), to: user_path(@conn, :show, @user.username), class: \"nav-link\" %></li>\n  <li class=\"nav-item\"><%= link gettext(\"Topic\"), to: user_topics_path(@conn, :topics, @user.username), class: \"nav-link\" %></li>\n  <li class=\"nav-item\"><%= link gettext(\"Reply\"), to: user_replies_path(@conn, :replies, @user.username), class: \"nav-link\" %></li>\n  <li class=\"nav-item\"><%= link gettext(\"Collection\"), to: user_collections_path(@conn, :collections, @user.username), class: \"nav-link\" %></li>\n  <li class=\"nav-item\"><%= link gettext(\"Following\"), to: user_following_path(@conn, :following, @user.username), class: \"nav-link\" %></li>\n  <li class=\"nav-item\"><%= link gettext(\"Follower\"), to: user_followers_path(@conn, :followers, @user.username), class: \"nav-link\" %></li>\n</ul>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/_repos.html.eex",
    "content": "<div class=\"card\" id=\"user_github_repos\">\n  <div class=\"card-header\">GitHub Public Repos</div>\n  <ul class=\"list-group list-group-flush\">\n    <%= for item <- github_repos(@object) do %>\n      <li class=\"list-group-item\">\n        <div class=\"title\">\n          <a target=\"_blank\" rel=\"nofollow\" href=\"<%= item[\"html_url\"] %>\"><%= item[\"name\"] %></a>\n          <span class=\"watchers\"><%= item[\"watchers\"] %></span>\n        </div>\n        <p class=\"desc\"><%= item[\"description\"] %></p>\n      </li>\n    <% end %>\n  </ul>\n  <div class=\"card-footer more\">\n    <a target=\"_blank\" rel=\"nofollow\" href=\"https://github.com/<%= github_account(@object) %>\">More on GitHub</a>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/_reward.html.eex",
    "content": "<div class=\"modal\" id=\"reward-modal\">\n  <div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\"><span aria-hidden=\"true\">×</span></button>\n      </div>\n      <div class=\"modal-body clearfix\">\n        <div class=\"message\">\n          <div class=\"user-info\"><img class=\"media-object avatar-48\" src=\"<%= qn_url(@user.avatar) %>\"></div>\n          <p><i class=\"fa fa-quote-left pull-left\"></i>如果觉得我的文章对您有用，请随意打赏。您的支持将鼓励我继续创作！<i class=\"fa fa-quote-right pull-right\"></i></p>\n        </div>\n        <div class=\"reward-image\"><img src=\"<%= qn_url(@user.wechat) %>\"></div>\n        <div class=\"reward-image\"><img src=\"<%= qn_url(@user.alipay) %>\"></div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/_teams.html.eex",
    "content": "<%= if @user.teams do %>\n  <div class=\"card user-teams\">\n    <div class=\"card-body\">\n      <%= for team <- @user.teams do %>\n        <%= link to: team_path(@conn, :show, team), title: team.name do %>\n          <img class=\"media-object avatar-48\" src=\"<%= team.avatar %>\" />\n        <% end %>\n      <% end %>\n    </div>\n  </div>\n<% end %>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/collections.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"sidebar col-md-4\" id='sidebar'>\n    <%= render \"_left.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-8\">\n    <%= render \"_menu.html\", assigns %>\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <table class=\"node-topics table\">\n          <tr class=\"head\">\n            <td class=\"opts\"><%= gettext \"Node\" %></td>\n            <td class=\"title\"><%= gettext \"Title\" %></td>\n          </tr>\n          <%= for {item, idx} <- Enum.with_index(@collections) do %>\n            <tr class=\"topic <%= cycle_tag(idx) %>\">\n              <td><%= link item.topic.node.name, to: topic_path(@conn, :index) %></td>\n              <td class=\"title\">\n                <%= link item.topic.title, title: item.topic.title, to: topic_path(@conn, :show, item.topic) %>\n              </td>\n            </tr>\n          <% end %>\n        </table>\n      </div>\n      <div class=\"card-footer clearfix\">\n        <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/followers.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"sidebar col-md-4\" id='sidebar'>\n    <%= render \"_left.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-8\">\n    <%= render \"_menu.html\", assigns %>\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <div class=\"row\">\n          <%= for item <- @followers do %>\n            <div class=\"col-xs-12 col-sm-6 col-md-4\">\n              <div class=\"media user-card\">\n                <div class=\"media-left mr-2\">\n                  <%= link to: user_path(@conn, :show, item.follower.username), title: item.follower.username do %>\n                    <img class=\"media-object avatar-48\" src=\"<%= qn_url(item.follower.avatar) %>\" />\n                  <% end %>\n                </div>\n                <div class=\"media-body\">\n                  <div class=\"media-heading\">\n                    <%= link item.follower.username, to: user_path(@conn, :show, item.follower.username)%>\n                  </div>\n                  <div class=\"infos\">\n                    <div class=\"item opts\">\n                      <%= if @current_user && @current_user.id != item.follower.id do %>\n                        <%= if has_followed?(@current_user, item.follower) do %>\n                          <%= link to: user_unfollow_path(@conn, :unfollow, item.follower.username), method: :post, class: \"button-follow-user unactive\" do %>\n                            <i class=\"fa fa-user\"></i> <span><%= gettext \"Unfollow\" %></span>\n                          <% end %>\n                        <% else %>\n                          <%= link to: user_follow_path(@conn, :follow, item.follower.username), method: :post, class: \"button-follow-user active\" do %>\n                            <i class=\"fa fa-user\"></i> <span><%= gettext \"Follow\" %></span>\n                          <% end %>\n                        <% end %>\n                      <% end %>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          <% end %>\n        </div>\n      </div>\n      <div class=\"card-footer clearfix\">\n        <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/following.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"sidebar col-md-4\" id='sidebar'>\n    <%= render \"_left.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-8\">\n    <%= render \"_menu.html\", assigns %>\n\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <div class=\"row\">\n          <%= for item <- @following do %>\n            <div class=\"col-xs-12 col-sm-6 col-md-4\">\n              <div class=\"media user-card\">\n                <div class=\"media-left mr-2\">\n                  <%= link to: user_path(@conn, :show, item.user.username), title: item.user.username do %>\n                    <img class=\"media-object avatar-48\" src=\"<%= qn_url(item.user.avatar) %>\" />\n                  <% end %>\n                </div>\n                <div class=\"media-body\">\n                  <div class=\"media-heading\">\n                    <%= link item.user.username, to: user_path(@conn, :show, item.user.username)%>\n                  </div>\n                  <div class=\"infos\">\n                    <div class=\"item opts\">\n                      <%= if @current_user && @current_user != item.user.id do %>\n                        <%= if has_followed?(@current_user, item.user) do %>\n                          <%= link to: user_unfollow_path(@conn, :unfollow, item.user.username), method: :post, class: \"button-follow-user unactive\" do %>\n                            <i class=\"fa fa-user\"></i> <span><%= gettext \"UnFollow\" %></span>\n                          <% end %>\n                        <% else %>\n                          <%= link to: user_follow_path(@conn, :follow, item.user.username), method: :post, class: \"button-follow-user active\" do %>\n                            <i class=\"fa fa-user\"></i> <span><%= gettext \"Follow\" %></span>\n                          <% end %>\n                        <% end %>\n                      <% end %>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n          <% end %>\n        </div>\n      </div>\n      <div class=\"card-footer clearfix\">\n        <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/forgot_password.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col\"></div>\n  <div class=\"col-md-6\">\n    <div class=\"card\">\n      <div class=\"card-header\"><%= gettext \"Forgot Password?\" %></div>\n      <div class=\"card-body\">\n        <form class=\"new_user\" id=\"new_user\" action=\"/users/sent_forgot_password_email\" accept-charset=\"UTF-8\" method=\"post\">\n          <input type=\"hidden\" name=\"_method\" value=\"post\" />\n          <input type=\"hidden\" name=\"_csrf_token\" value=\"<%= get_csrf_token() %>\" />\n          <div class=\"form-group\">\n            <input type=\"email\" class=\"form-control input-lg\" placeholder=\"Email\" name=\"user[email]\" id=\"user_email\">\n            <p class=\"form-text text-muted\">\n              <%= gettext \"Will send a special link to retrieve your password to your email address. You can enter the reset password page by changing the link.\" %>\n            </p>\n          </div>\n          <div class=\"form-actions\">\n            <input type=\"submit\" name=\"commit\" value=\"Find your Password\" class=\"btn btn-primary\">\n          </div>\n        </form>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\"></div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/user/index.html.eex",
    "content": "<div id=\"users\">\n  <div id=\"hot_users\" class=\"user-list card\">\n    <div class=\"card-header\">\n      <strong><%= gettext \"TOP 100 active users\" %></strong>\n      <div class=\"pull-right\"><%= gettext \"Current have %{user_count} users joined Elixir Mipha\", user_count: @user_count %></div>\n    </div>\n    <div class=\"card-body\">\n      <div class=\"row\">\n        <%= for user <- @users do %>\n          <div class=\"col-xs-12 col-sm-6 col-md-4\">\n            <div class=\"media user-card\">\n              <div class=\"media-left mr-2\">\n                <%= link to: user_path(@conn, :show, user.username), title: user.username do %>\n                  <img class=\"media-object avatar-48\" src=\"<%= qn_url(user.avatar) %>\" />\n                <% end %>\n              </div>\n              <div class=\"media-body\">\n                <div class=\"media-heading\">\n                  <%= link user.username, to: user_path(@conn, :show, user.username), title: user.username, class: \"user-name\" %>\n                </div>\n                <div class=\"infos\">\n                    <div class=\"item opts\">\n                      <%= if @current_user && @current_user.id != user.id do %>\n                        <%= if has_followed?(@current_user, user) do %>\n                          <%= link to: user_unfollow_path(@conn, :unfollow, user.username), method: :post, class: \"button-follow-user unactive\" do %>\n                            <i class=\"fa fa-user\"></i> <span><%= gettext \"Unfollow\" %></span>\n                          <% end %>\n                        <% else %>\n                          <%= link to: user_follow_path(@conn, :follow, user.username), method: :post, class: \"button-follow-user active\" do %>\n                            <i class=\"fa fa-user\"></i> <span><%= gettext \"Follow\" %></span>\n                          <% end %>\n                        <% end %>\n                      <% end %>\n                    </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        <% end %>\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/user/invalid_token.html.eex",
    "content": "<h1>Invalid token</h1>"
  },
  {
    "path": "lib/mipha_web/templates/user/replies.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"sidebar col-md-4\" id='sidebar'>\n    <%= render \"_left.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-8\">\n    <%= render \"_menu.html\", assigns %>\n    <div class=\"card\">\n      <ul class=\"list-group list-group-flush recent-replies\">\n        <%= for item <- @replies do %>\n          <li class=\"list-group-item\">\n            <div class=\"title\">\n              <%= link item.topic.title, to: topic_path(@conn, :show, item.topic) %>\n              <span class=\"info\">at <%= time_tag(item.updated_at) %></span>\n            </div>\n            <div class=\"body markdown\">\n              <p><%= markdown(item.content) %></p>\n            </div>\n          </li>\n        <% end %>\n      </ul>\n\n      <div class=\"card-footer clearfix\">\n        <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/reset_password.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"col\"></div>\n  <div class=\"col-md-6\">\n    <div class=\"card\">\n      <div class=\"card-header\"><%= gettext \"Reset Password\" %></div>\n      <div class=\"card-body\">\n        <%= form_for @changeset, user_path(@conn, :update_password), fn f -> %>\n          <input type=\"hidden\" value=\"<%= @token %>\" name=\"user[reset_password_token]\" id=\"user_reset_password_token\">\n          <div class=\"form-group\">\n            <%= password_input f, :password, class: \"form-control\", required: true, placeholder: \"Password\" %>\n            <%= error_tag f, :password %>\n          </div>\n          <div class=\"form-group\">\n            <%= password_input f, :password_confirmation, class: \"form-control\", required: true, placeholder: \"Confirmation Password\" %>\n            <%= error_tag f, :password_confirmation %>\n          </div>\n          <div class=\"form-actions\">\n            <%= submit gettext(\"Sumbit\"), class: \"btn btn-success\" %>\n          </div>\n        <% end %>\n      </div>\n    </div>\n  </div>\n  <div class=\"col\"></div>\n</div>"
  },
  {
    "path": "lib/mipha_web/templates/user/show.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"sidebar col-md-4\" id='sidebar'>\n    <%= render \"_left.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-8\">\n    <%= render \"_menu.html\", assigns %>\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <div class=\"card-text\">Placeholder</div>\n      </div>\n    </div>\n    <div class=\"tabbable\">\n      <ul class=\"nav nav-tabs\">\n        <li class=\"nav-item\">\n          <a href=\"#topics\" class=\"nav-link active\" data-toggle=\"tab\">\n            <%= gettext \"Hot Topics\" %>\n          </a>\n        </li>\n        <li class=\"nav-item\">\n          <a href=\"#replies\" class=\"nav-link\" data-toggle=\"tab\">\n            <%= gettext \"Recent Repleis\" %>\n          </a>\n        </li>\n        <li class=\"nav-item\">\n          <a href=\"#description\" class=\"nav-link\" data-toggle=\"tab\">\n            <%= gettext \"Introduction\" %>\n          </a>\n        </li>\n      </ul>\n      <div class=\"tab-content\">\n        <div class=\"tab-pane active recent-topics\" id=\"topics\">\n          <div class=\"card\">\n            <ul class=\"list-group list-group-flush\">\n              <%= for item <- @topics do %>\n                <li class=\"list-group-item\">\n                  <div class=\"title\">\n                    <a class=\"node\" href=\"/topics?node_id=<%= item.node.id %>\"><%= item.node.name %></a>\n                    <%= link item.title, to: topic_path(@conn, :show, item), title: item.title %>\n                    <%= if item.type == :featured do %>\n                      <i title=\"\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"></i>\n                    <% end %>\n                  </div>\n                  <div class=\"info\">\n                    <span class=\"counter\"><%= item.star_count %> 个赞 • <%= item.reply_count %> 条回复</span>\n                  </div>\n                </li>\n              <% end %>\n            </ul>\n          </div>\n        </div>\n        <div class=\"tab-pane\" id=\"replies\">\n          <div class=\"card\">\n            <ul class=\"list-group list-group-flush recent-replies\">\n              <%= for item <- @replies do %>\n                <%= unless is_nil(item.topic) do %>\n                  <li class=\"list-group-item\">\n                    <div class=\"title\">\n                      <%= link item.topic.title, to: topic_path(@conn, :show, item.topic) %>\n                      <span class=\"info\">at <%= time_tag(item.updated_at) %></span>\n                    </div>\n                    <div class=\"body markdown\">\n                      <p><%= markdown(item.content) %></p>\n                    </div>\n                  </li>\n                <% end %>\n              <% end %>\n            </ul>\n          </div>\n        </div>\n        <div class=\"tab-pane\" id=\"description\">\n          <div class=\"card bio\">\n            <div class=\"card-body markdown\">\n              <p><%= @user.bio %></p>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/topics.html.eex",
    "content": "<div class=\"row\">\n  <div class=\"sidebar col-md-4\" id='sidebar'>\n    <%= render \"_left.html\", assigns %>\n  </div>\n\n  <div class=\"col-md-8\">\n    <%= render \"_menu.html\", assigns %>\n    <div class=\"card\">\n      <div class=\"card-body\">\n        <table class=\"node-topics table\">\n          <tr class=\"head\">\n            <td class=\"node hide-ios\"><%= gettext \"Node\" %></td>\n            <td class=\"title\"><%= gettext \"Title\" %></td>\n            <td class=\"replies\"><%= gettext \"Replies/Hits\" %></td>\n          </tr>\n          <%= for {topic, idx} <- Enum.with_index(@topics) do %>\n            <tr class=\"topic <%= cycle_tag(idx) %> \">\n              <td class=\"node hide-ios\">\n                <%= link topic.node.name, to: topic_path(@conn, :index), class: \"node\" %>\n              </td>\n              <td class=\"title\">\n                <%= link topic.title, to: topic_path(@conn, :show, topic) %>\n                <%= topic_featured_tag(topic) %>\n                <%= time_tag(topic.inserted_at) %>\n              </td>\n              <td class=\"replies\"><%= topic.reply_count %>/<%= topic.visit_count %></td>\n            </tr>\n          <% end %>\n        </table>\n      </div>\n      <div class=\"card-footer clearfix\">\n        <%= render MiphaWeb.SharedView, \"_pagination.html\", assigns %>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "lib/mipha_web/templates/user/verified.html.eex",
    "content": "<h1>Verified successfully.</h1>"
  },
  {
    "path": "lib/mipha_web/views/admin/company_view.ex",
    "content": "defmodule MiphaWeb.Admin.CompanyView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/admin/node_view.ex",
    "content": "defmodule MiphaWeb.Admin.NodeView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/admin/notification_view.ex",
    "content": "defmodule MiphaWeb.Admin.NotificationView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/admin/page_view.ex",
    "content": "defmodule MiphaWeb.Admin.PageView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/admin/reply_view.ex",
    "content": "defmodule MiphaWeb.Admin.ReplyView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/admin/team_view.ex",
    "content": "defmodule MiphaWeb.Admin.TeamView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/admin/topic_view.ex",
    "content": "defmodule MiphaWeb.Admin.TopicView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/admin/user_view.ex",
    "content": "defmodule MiphaWeb.Admin.UserView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/auth_view.ex",
    "content": "defmodule MiphaWeb.AuthView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/company_view.ex",
    "content": "defmodule MiphaWeb.CompanyView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/email_view.ex",
    "content": "defmodule MiphaWeb.EmailView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/error_helpers.ex",
    "content": "defmodule MiphaWeb.ErrorHelpers do\n  @moduledoc \"\"\"\n  Conveniences for translating and building error messages.\n  \"\"\"\n\n  use Phoenix.HTML\n\n  @doc \"\"\"\n  Generates tag for inlined form input errors.\n  \"\"\"\n  def error_tag(form, field) do\n    Enum.map(Keyword.get_values(form.errors, field), fn error ->\n      content_tag(:span, translate_error(error), class: \"help-block\")\n    end)\n  end\n\n  @doc \"\"\"\n  Translates an error message using gettext.\n  \"\"\"\n  def translate_error({msg, opts}) do\n    # When using gettext, we typically pass the strings we want\n    # to translate as a static argument:\n    #\n    #     # Translate \"is invalid\" in the \"errors\" domain\n    #     dgettext \"errors\", \"is invalid\"\n    #\n    #     # Translate the number of files with plural rules\n    #     dngettext \"errors\", \"1 file\", \"%{count} files\", count\n    #\n    # Because the error messages we show in our forms and APIs\n    # are defined inside Ecto, we need to translate them dynamically.\n    # This requires us to call the Gettext module passing our gettext\n    # backend as first argument.\n    #\n    # Note we use the \"errors\" domain, which means translations\n    # should be written to the errors.po file. The :count option is\n    # set by Ecto and indicates we should also apply plural rules.\n    if count = opts[:count] do\n      Gettext.dngettext(MiphaWeb.Gettext, \"errors\", msg, msg, count, opts)\n    else\n      Gettext.dgettext(MiphaWeb.Gettext, \"errors\", msg, opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/views/error_view.ex",
    "content": "defmodule MiphaWeb.ErrorView do\n  use MiphaWeb, :view\n\n  # If you want to customize a particular status code\n  # for a certain format, you may uncomment below.\n  # def render(\"500.html\", _assigns) do\n  #   \"Internal Server Error\"\n  # end\n\n  # By default, Phoenix returns the status message from\n  # the template name. For example, \"404.html\" becomes\n  # \"Not Found\".\n  def template_not_found(template, _assigns) do\n    Phoenix.Controller.status_message_from_template(template)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/views/layout_view.ex",
    "content": "defmodule MiphaWeb.LayoutView do\n  use MiphaWeb, :view\n\n  alias Mipha.Notifications\n\n  @doc \"\"\"\n  判断是否有未读的 notification\n  \"\"\"\n  def has_unread_notification?(user) do\n    Notifications.unread_notification_count(user) > 0\n  end\n\n  @doc \"\"\"\n  获取未读的 notification 个数\n  \"\"\"\n  def unread_notification_count(user) do\n    Notifications.unread_notification_count(user)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/views/location_view.ex",
    "content": "defmodule MiphaWeb.LocationView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/notification_view.ex",
    "content": "defmodule MiphaWeb.NotificationView do\n  use MiphaWeb, :view\n\n  alias Mipha.Notifications\n\n  def group_by(notifications) do\n    notifications\n    |> Enum.group_by(&Timex.format!(&1.updated_at, \"{YYYY}-{0M}-{D}\"))\n  end\n\n  def transfer_action(notification) do\n    target_object = Notifications.object(notification)\n    actor = Notifications.actor(notification)\n\n    render(\"_#{Atom.to_string(notification.action)}.html\",\n      actor: actor,\n      target_object: target_object\n    )\n  end\n\n  def hour_format(notification) do\n    Timex.format!(notification.updated_at, \"{h24}:{m}\")\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/views/page_view.ex",
    "content": "defmodule MiphaWeb.PageView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/reply_view.ex",
    "content": "defmodule MiphaWeb.ReplyView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/search_view.ex",
    "content": "defmodule MiphaWeb.SearchView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/session_view.ex",
    "content": "defmodule MiphaWeb.SessionView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/setting_view.ex",
    "content": "defmodule MiphaWeb.SettingView do\n  use MiphaWeb, :view\n\n  alias Mipha.Accounts\n\n  @doc \"\"\"\n  location select option\n  \"\"\"\n  def location_option do\n    Accounts.list_locations()\n    |> Enum.map(&{&1.name, &1.id})\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/views/shared_view.ex",
    "content": "defmodule MiphaWeb.SharedView do\n  use MiphaWeb, :view\nend\n"
  },
  {
    "path": "lib/mipha_web/views/tag_helpers.ex",
    "content": "defmodule MiphaWeb.TagHelpers do\n  @moduledoc \"\"\"\n  View helpers for generating particular tags with some internal logic.\n  \"\"\"\n\n  import Phoenix.HTML.Tag\n  require Integer\n  alias Mipha.Qiniu\n\n  @doc \"\"\"\n  Generate an abbr to wrap an ISO time to be rendered nicely via JS in the frontend.\n  \"\"\"\n  def time_tag(time) do\n    time_string =\n      time\n      |> Timex.format!(\"{ISO:Extended:Z}\")\n\n    content_tag(:addr, class: \"timeago\", title: time_string) do\n      time_string\n    end\n  end\n\n  @doc \"\"\"\n  Returns user level.\n  \"\"\"\n  def user_level_tag(user) do\n    level_name = (user.is_admin && \"管理员\") || \"会员\"\n    level_class = (user.is_admin && \"badge-alert\") || \"badge-success\"\n    content_tag(:span, level_name, class: \"badge #{level_class} role\")\n  end\n\n  @doc \"\"\"\n  Return if rem(number) == 0, return odd, else return even.\n  \"\"\"\n  def cycle_tag(number) do\n    (Integer.is_odd(number) && \"odd\") || \"even\"\n  end\n\n  @doc \"\"\"\n  Return featured if type == :featured\n  \"\"\"\n  def topic_featured_tag(topic) do\n    if topic.type == :featured do\n      content_tag(:i, \"\", title: \"精华帖\", class: \"fa fa-diamond\", style: \"color: red;\")\n    end\n  end\n\n  def qn_url(string) do\n    Qiniu.q_url(string)\n  end\n\n  def topic_title_tag(topic, reply \\\\ nil) do\n    ahref =\n      if is_nil(reply), do: \"/topics/#{topic.id}\", else: \"/topics/#{topic.id}#reply#{reply.id}\"\n\n    content_tag(:a, topic.title, href: ahref, title: topic.title)\n  end\n\n  def user_name_tag(user) do\n    content_tag(:a, user.username, href: \"/u/#{user.username}\", title: user.username)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/views/team_view.ex",
    "content": "defmodule MiphaWeb.TeamView do\n  use MiphaWeb, :view\n\n  def is_owner?(team, user) do\n    team.owner_id == user.id\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/views/topic_view.ex",
    "content": "defmodule MiphaWeb.TopicView do\n  use MiphaWeb, :view\n\n  alias Mipha.{\n    Stars,\n    Collections,\n    Topics,\n    Accounts,\n    Replies\n  }\n\n  @doc \"\"\"\n  判断是否已 Star\n  \"\"\"\n  def has_starred?(clauses) do\n    if is_nil(Keyword.get(clauses, :user)), do: false, else: Stars.has_starred?(clauses)\n  end\n\n  @doc \"\"\"\n  判断是否已收藏该帖。\n  \"\"\"\n  def has_collected?(current_user, topic) do\n    if is_nil(current_user),\n      do: false,\n      else: Collections.has_collected?(user: current_user, topic: topic)\n  end\n\n  @doc \"\"\"\n  获取全站 topic 个数\n  \"\"\"\n  def get_topic_count do\n    Topics.get_total_topic_count()\n  end\n\n  @doc \"\"\"\n  获取全站 topic 个数\n  \"\"\"\n  def get_reply_count do\n    Replies.get_total_reply_count()\n  end\n\n  @doc \"\"\"\n  获取全站 user 个数\n  \"\"\"\n  def get_user_count do\n    Accounts.get_total_user_count()\n  end\n\n  @doc \"\"\"\n  是否是受欢迎的帖子评论。\n  \"\"\"\n  def popular(reply) do\n    if reply.star_count >= 5, do: \"popular\"\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/views/user_view.ex",
    "content": "defmodule MiphaWeb.UserView do\n  use MiphaWeb, :view\n\n  alias Mipha.{\n    Accounts,\n    Topics,\n    Replies,\n    Follows,\n    Collections\n  }\n\n  def format_inserted_date(datetime) do\n    Timex.format!(datetime, \"{YYYY}-{0M}-{0D}\")\n  end\n\n  def user_topics_count(user) do\n    Topics.get_topic_count(user)\n  end\n\n  def user_replies_count(user) do\n    Replies.get_reply_count(user: user)\n  end\n\n  def user_followers_count(user) do\n    Follows.get_follower_count(user: user)\n  end\n\n  def user_following_count(user) do\n    Follows.get_followee_count(user)\n  end\n\n  def user_collections_count(user) do\n    Collections.get_collection_count(user)\n  end\n\n  @doc \"\"\"\n  Returen `true` if the current_user followed target_user, `false` otherwise.\n  \"\"\"\n  def has_followed?(current_user, user) do\n    Follows.has_followed?(follower: current_user, user: user)\n  end\n\n  def github_repos(target) do\n    Accounts.github_repositories(target)\n  end\n\n  def github_account(target) do\n    Accounts.github_handle(target)\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web/views/view_helpers.ex",
    "content": "defmodule MiphaWeb.ViewHelpers do\n  @moduledoc \"\"\"\n  View helpers for global funcs we find useful.\n  \"\"\"\n  import Phoenix.HTML, only: [raw: 1]\n  alias Mipha.Markdown\n\n  def markdown(body) do\n    body\n    |> Markdown.render()\n    |> raw()\n  end\nend\n"
  },
  {
    "path": "lib/mipha_web.ex",
    "content": "defmodule MiphaWeb do\n  @moduledoc \"\"\"\n  The entrypoint for defining your web interface, such\n  as controllers, views, channels and so on.\n\n  This can be used in your application as:\n\n      use MiphaWeb, :controller\n      use MiphaWeb, :view\n\n  The definitions below will be executed for every view,\n  controller, etc, so keep them short and clean, focused\n  on imports, uses and aliases.\n\n  Do NOT define functions inside the quoted expressions\n  below. Instead, define any helper function in modules\n  and import those modules here.\n  \"\"\"\n\n  def controller do\n    quote do\n      use Phoenix.Controller, namespace: MiphaWeb\n      import Plug.Conn\n      import MiphaWeb.Router.Helpers\n      import MiphaWeb.Gettext\n      import MiphaWeb.Session\n    end\n  end\n\n  def view do\n    quote do\n      use Phoenix.View,\n        root: \"lib/mipha_web/templates\",\n        namespace: MiphaWeb\n\n      # Import convenience functions from controllers\n      import Phoenix.Controller, only: [get_flash: 2, view_module: 1, get_csrf_token: 0]\n\n      # Use all HTML functionality (forms, tags, etc)\n      use Phoenix.HTML\n\n      import MiphaWeb.Router.Helpers\n      import MiphaWeb.ErrorHelpers\n      import MiphaWeb.Gettext\n      import MiphaWeb.Session\n      import MiphaWeb.ViewHelpers\n      import MiphaWeb.TagHelpers\n      import Turbo.HTML\n    end\n  end\n\n  def router do\n    quote do\n      use Phoenix.Router\n      import Plug.Conn\n      import Phoenix.Controller\n    end\n  end\n\n  def channel do\n    quote do\n      use Phoenix.Channel\n      import MiphaWeb.Gettext\n    end\n  end\n\n  @doc \"\"\"\n  When used, dispatch to the appropriate controller/view/etc.\n  \"\"\"\n  defmacro __using__(which) when is_atom(which) do\n    apply(__MODULE__, which, [])\n  end\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule Mipha.Mixfile do\n  use Mix.Project\n\n  def project do\n    [\n      app: :mipha,\n      version: \"0.0.1\",\n      elixir: \"~> 1.7\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      compilers: [:phoenix, :gettext] ++ Mix.compilers(),\n      start_permanent: Mix.env() == :prod,\n      aliases: aliases(),\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls],\n      preferred_cli_env: [\n        coveralls: :test,\n        \"coveralls.detail\": :test,\n        \"coveralls.post\": :test,\n        \"coveralls.html\": :test\n      ]\n    ]\n  end\n\n  # Configuration for the OTP application.\n  #\n  # Type `mix help compile.app` for more information.\n  def application do\n    [\n      mod: {Mipha.Application, []},\n      extra_applications: [:logger, :runtime_tools]\n    ]\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  # Specifies your project dependencies.\n  #\n  # Type `mix help deps` for examples and options.\n  defp deps do\n    [\n      {:phoenix, \"~> 1.5.1\"},\n      {:phoenix_pubsub, \"~> 2.0\"},\n      {:phoenix_ecto, \"~> 4.0\"},\n      {:ecto_sql, \"~> 3.0\"},\n      {:postgrex, \">= 0.0.0\"},\n      {:phoenix_html, \"~> 2.10\"},\n      {:phoenix_live_reload, \"~> 1.0\", only: :dev},\n      {:gettext, \"~> 0.11\"},\n      {:jason, \"~> 1.1\"},\n      {:plug_cowboy, \"~> 2.0\"},\n      {:plug, \"~> 1.7\"},\n      {:credo, \"~> 1.0\"},\n      {:comeonin, \"~> 4.1\"},\n      {:bcrypt_elixir, \"~> 1.0\"},\n      {:ueberauth, \"~> 0.5.0\"},\n      {:ueberauth_identity, \"~> 0.2.3\"},\n      {:ueberauth_github, \"~> 0.7.0\"},\n      {:faker, \"~> 0.10.0\"},\n      {:earmark, \"~> 1.2.6\"},\n      {:html_sanitize_ex, \"~> 1.3\"},\n      {:timex, \"~> 3.3\"},\n      {:ecto_enum, \"~> 1.1\"},\n      {:qiniu, \"~> 0.4.0\"},\n      {:exmoji, \"~> 0.2.2\"},\n      {:bamboo, \"~> 1.0\"},\n      {:bamboo_smtp, \"~> 2.1\"},\n      {:cachex, \"~> 3.3.0\"},\n      {:poison, \"~> 4.0\", override: true},\n      {:captcha, github: \"zven21/elixir-captcha\"},\n      {:turbo_ecto, github: \"zven21/turbo_ecto\"},\n      {:turbo_html, github: \"zven21/turbo_html\"},\n      {:remote_ip, \"~> 0.1.4\"},\n      {:plug_attack, \"~> 0.3.1\"},\n      {:sentry, \"~> 6.4\"},\n      {:ex_machina, \"~> 2.4.0\"},\n      {:excoveralls, \"~> 0.10\", only: :test},\n      {:git_hooks, \"~> 0.5.0\", only: [:test, :dev], runtime: false}\n    ]\n  end\n\n  # Aliases are shortcuts or tasks specific to the current project.\n  # For example, to create, migrate and run the seeds file at once:\n  #\n  #     $ mix ecto.setup\n  #\n  # See the documentation for `Mix` for more info on aliases.\n  defp aliases do\n    [\n      \"ecto.setup\": [\"ecto.create\", \"ecto.migrate\", \"run priv/repo/seeds.exs\"],\n      \"ecto.reset\": [\"ecto.drop\", \"ecto.setup\"],\n      test: [\"ecto.create --quiet\", \"ecto.migrate\", \"test\"]\n    ]\n  end\nend\n"
  },
  {
    "path": "phoenix_static_buildpack.config",
    "content": "# We can set the version of Node to use for the app here\nnode_version=9.0.0\n\n# We can set the version of NPM to use for the app here\nnpm_version=5.6.0\n\nclean_cache=true"
  },
  {
    "path": "priv/gettext/default.pot",
    "content": "#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:5\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:10\n#: lib/mipha_web/templates/topic/_replies.html.eex:37\nmsgid \"%{star_count} stars\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:63\nmsgid \"Account Profile\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:7\nmsgid \"All Nodes\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:18\nmsgid \"Change Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/notification/index.html.eex:6\nmsgid \"Clean all\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:58\nmsgid \"Closed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:20\n#: lib/mipha_web/templates/topic/_right_sidebar.html.eex:28\n#: lib/mipha_web/templates/user/_left.html.eex:54\n#: lib/mipha_web/templates/user/_menu.html.eex:5\nmsgid \"Collection\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:234\nmsgid \"Collection failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:229\nmsgid \"Collection successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:29\nmsgid \"Confirmation Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:136\nmsgid \"Create topic failed, pls select node_id.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_topic.html.eex:31\nmsgid \"Created at\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/index.html.eex:5\nmsgid \"Current have %{user_count} users joined Elixir Mipha\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:17\nmsgid \"Default Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/reply/edit.html.eex:18\nmsgid \"Delete\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:12\nmsgid \"Details\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/reply/edit.html.eex:4\nmsgid \"Edit Reply\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/edit.html.eex:3\nmsgid \"Edit Topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:26\nmsgid \"Edit reply\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:29\nmsgid \"Failed to authenticate.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:12\nmsgid \"Featured\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:72\nmsgid \"Featured topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:68\n#: lib/mipha_web/templates/user/followers.html.eex:32\n#: lib/mipha_web/templates/user/following.html.eex:33\n#: lib/mipha_web/templates/user/index.html.eex:30\nmsgid \"Follow\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:97\nmsgid \"Follow failed.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:92\nmsgid \"Follow successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:46\n#: lib/mipha_web/templates/user/_menu.html.eex:7\nmsgid \"Follower\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:50\n#: lib/mipha_web/templates/user/_menu.html.eex:6\nmsgid \"Following\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/forgot_password.html.eex:5\nmsgid \"Forgot Password?\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:43\nmsgid \"Forgot your password?\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:14\nmsgid \"Friendship community\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:35\nmsgid \"Github\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/page/_locations.html.eex:3\nmsgid \"Hot Cities\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/show.html.eex:17\nmsgid \"Hot Topics\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_menu.html.eex:2\nmsgid \"Info\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/show.html.eex:27\nmsgid \"Introduction\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:72\nmsgid \"Invalid password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:13\nmsgid \"Job\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_topic.html.eex:24\n#: lib/mipha_web/templates/topic/show.html.eex:18\nmsgid \"Last by\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:13\nmsgid \"Location\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:77\nmsgid \"Login successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/notification/index.html.eex:5\nmsgid \"Mark all readed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:42\nmsgid \"Mark featured\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/page/markdown.html.eex:3\n#: lib/mipha_web/templates/topic/_form.html.eex:28\n#: lib/mipha_web/templates/topic/_handle_reply.html.eex:27\nmsgid \"Markdown Guide\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:23\nmsgid \"New Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:52\n#: lib/mipha_web/templates/topic/_right.html.eex:3\n#: lib/mipha_web/templates/topic/new.html.eex:3\nmsgid \"New Topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:14\nmsgid \"No Reply\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/collections.html.eex:12\n#: lib/mipha_web/templates/user/topics.html.eex:12\nmsgid \"Node\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_node_selector.html.eex:5\n#: lib/mipha_web/templates/topic/_node_selector.html.eex:12\n#: lib/mipha_web/templates/topic/_nodes.html.eex:2\nmsgid \"Nodes\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:11\nmsgid \"Normal\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:79\nmsgid \"Normal topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:7\nmsgid \"Note\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:8\nmsgid \"Note! You will need to log in again after updating your password.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/notification/index.html.eex:3\nmsgid \"Notifications\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:65\nmsgid \"Opened\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:44\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:32\nmsgid \"Pin\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:13\nmsgid \"Popular\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_editor_toolbar.html.eex:34\nmsgid \"Preview\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:6\nmsgid \"Profile\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:43\nmsgid \"Profile updated failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:38\nmsgid \"Profile updated successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/show.html.eex:22\nmsgid \"Recent Repleis\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/session_controller.ex:48\nmsgid \"Register failed. pls try again.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/session_controller.ex:62\nmsgid \"Registered successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:18\nmsgid \"Remember me (2 months)\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:45\nmsgid \"Replied to\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:49\nmsgid \"Replies Count: %{reply_count}\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/topics.html.eex:14\nmsgid \"Replies/Hits\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_handle_reply.html.eex:5\n#: lib/mipha_web/templates/user/_menu.html.eex:4\nmsgid \"Reply\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:61\nmsgid \"Reply deleted successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:51\nmsgid \"Reply updated error.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:46\nmsgid \"Reply updated successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/reset_password.html.eex:5\nmsgid \"Reset Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:179\nmsgid \"Reset password successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:24\n#: lib/mipha_web/templates/user/_left.html.eex:81\nmsgid \"Reward\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/reply/edit.html.eex:17\nmsgid \"Save\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:30\nmsgid \"Setting\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:5\n#: lib/mipha_web/templates/auth/login.html.eex:23\n#: lib/mipha_web/templates/layout/_header.html.eex:71\nmsgid \"Sign In\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:66\nmsgid \"Sign Out\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:42\n#: lib/mipha_web/templates/layout/_header.html.eex:70\n#: lib/mipha_web/templates/session/new.html.eex:5\n#: lib/mipha_web/templates/session/new.html.eex:33\nmsgid \"Register\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:32\nmsgid \"Sign in with other services\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:15\nmsgid \"Sign out successful\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:19\nmsgid \"Source Code\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:79\n#: lib/mipha_web/controllers/topic_controller.ex:197\nmsgid \"Star failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:74\n#: lib/mipha_web/controllers/topic_controller.ex:192\nmsgid \"Star successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:45\nmsgid \"Statistics\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_form.html.eex:25\n#: lib/mipha_web/templates/topic/_handle_reply.html.eex:19\nmsgid \"Submit\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/reset_password.html.eex:18\nmsgid \"Sumbit\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/index.html.eex:4\nmsgid \"TOP 100 active users\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:33\nmsgid \"Tagline\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/team/_nav.html.eex:6\nmsgid \"Team People\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/team/_nav.html.eex:3\nmsgid \"Team Topics\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:142\nmsgid \"The email is invalid.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:167\nmsgid \"The verification link is invalid.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/collections.html.eex:13\n#: lib/mipha_web/templates/user/topics.html.eex:13\nmsgid \"Title\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:10\n#: lib/mipha_web/templates/user/_menu.html.eex:3\nmsgid \"Topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:130\nmsgid \"Topic created successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:175\nmsgid \"Topic deleted successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:146\nmsgid \"Topic updated successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:48\nmsgid \"Topics Count: %{topic_count}\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:3\nmsgid \"Total\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right_sidebar.html.eex:37\nmsgid \"Total %{topic_count} replies\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:63\n#: lib/mipha_web/templates/user/following.html.eex:29\nmsgid \"UnFollow\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:16\n#: lib/mipha_web/templates/topic/_right_sidebar.html.eex:24\nmsgid \"Uncollection\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:253\nmsgid \"Uncollection failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:248\nmsgid \"Uncollection successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/followers.html.eex:28\n#: lib/mipha_web/templates/user/index.html.eex:26\nmsgid \"Unfollow\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:116\nmsgid \"Unfollow failed.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:111\nmsgid \"Unfollow successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:38\nmsgid \"Unmark featured\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:51\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:28\nmsgid \"Unpin\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:98\n#: lib/mipha_web/controllers/topic_controller.ex:215\nmsgid \"Unstar failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:93\n#: lib/mipha_web/controllers/topic_controller.ex:211\nmsgid \"Unstar successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:38\n#: lib/mipha_web/templates/setting/show.html.eex:41\nmsgid \"Update\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:35\nmsgid \"Update Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/show.html.eex:25\nmsgid \"Upload avatar\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:16\nmsgid \"User\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:14\nmsgid \"User not exist.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:57\n#: lib/mipha_web/controllers/setting_controller.ex:101\nmsgid \"User updated failed.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:67\nmsgid \"User updated password successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:52\n#: lib/mipha_web/controllers/setting_controller.ex:96\nmsgid \"User updated successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:47\nmsgid \"Users Count: %{user_count}\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:28\nmsgid \"Website\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/forgot_password.html.eex:13\nmsgid \"Will send a special link to retrieve your password to your email address. You can enter the reset password page by changing the link.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:86\nmsgid \"You has logged in\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:137\nmsgid \"You will receive an email to reset your password.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/session_controller.ex:36\nmsgid \"captcha error, pls input again.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/show.html.eex:25\nmsgid \"hits\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_topic.html.eex:28\n#: lib/mipha_web/templates/topic/show.html.eex:20\nmsgid \"replied at\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:4\nmsgid \"replies\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/notification_controller.ex:36\nmsgid \"Cleaned notification successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/notification_controller.ex:26\nmsgid \"Marked all notifications readed.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/show.html.eex:18\nmsgid \"Public Email\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:9\nmsgid \"Mipha is open source elixir forum, for more information, please visit\"\nmsgstr \"\"\n"
  },
  {
    "path": "priv/gettext/en/LC_MESSAGES/default.po",
    "content": "#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:5\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:10\n#: lib/mipha_web/templates/topic/_replies.html.eex:37\nmsgid \"%{star_count} stars\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:63\nmsgid \"Account Profile\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:7\nmsgid \"All Nodes\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:18\nmsgid \"Change Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/notification/index.html.eex:6\nmsgid \"Clean all\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:58\nmsgid \"Closed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:20\n#: lib/mipha_web/templates/topic/_right_sidebar.html.eex:28\n#: lib/mipha_web/templates/user/_left.html.eex:54\n#: lib/mipha_web/templates/user/_menu.html.eex:5\nmsgid \"Collection\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:234\nmsgid \"Collection failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:229\nmsgid \"Collection successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:29\nmsgid \"Confirmation Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:136\nmsgid \"Create topic failed, pls select node_id.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_topic.html.eex:31\nmsgid \"Created at\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/index.html.eex:5\nmsgid \"Current have %{user_count} users joined Elixir Mipha\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:17\nmsgid \"Default Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/reply/edit.html.eex:18\nmsgid \"Delete\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:12\nmsgid \"Details\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/reply/edit.html.eex:4\nmsgid \"Edit Reply\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/edit.html.eex:3\nmsgid \"Edit Topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:26\nmsgid \"Edit reply\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:29\nmsgid \"Failed to authenticate.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:12\nmsgid \"Featured\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:72\nmsgid \"Featured topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:68\n#: lib/mipha_web/templates/user/followers.html.eex:32\n#: lib/mipha_web/templates/user/following.html.eex:33\n#: lib/mipha_web/templates/user/index.html.eex:30\nmsgid \"Follow\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:97\nmsgid \"Follow failed.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:92\nmsgid \"Follow successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:46\n#: lib/mipha_web/templates/user/_menu.html.eex:7\nmsgid \"Follower\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:50\n#: lib/mipha_web/templates/user/_menu.html.eex:6\nmsgid \"Following\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/forgot_password.html.eex:5\nmsgid \"Forgot Password?\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:43\nmsgid \"Forgot your password?\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:14\nmsgid \"Friendship community\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:35\nmsgid \"Github\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/page/_locations.html.eex:3\nmsgid \"Hot Cities\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/show.html.eex:17\nmsgid \"Hot Topics\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_menu.html.eex:2\nmsgid \"Info\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/show.html.eex:27\nmsgid \"Introduction\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:72\nmsgid \"Invalid password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:9\nmsgid \"It's open source project, for more information, please visit: \"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:13\nmsgid \"Job\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_topic.html.eex:24\n#: lib/mipha_web/templates/topic/show.html.eex:18\nmsgid \"Last by\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:13\nmsgid \"Location\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:77\nmsgid \"Login successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/notification/index.html.eex:5\nmsgid \"Mark all readed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:42\nmsgid \"Mark featured\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/page/markdown.html.eex:3\n#: lib/mipha_web/templates/topic/_form.html.eex:28\n#: lib/mipha_web/templates/topic/_handle_reply.html.eex:27\nmsgid \"Markdown Guide\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:23\nmsgid \"New Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:52\n#: lib/mipha_web/templates/topic/_right.html.eex:3\n#: lib/mipha_web/templates/topic/new.html.eex:3\nmsgid \"New Topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:14\nmsgid \"No Reply\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/collections.html.eex:12\n#: lib/mipha_web/templates/user/topics.html.eex:12\nmsgid \"Node\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_node_selector.html.eex:5\n#: lib/mipha_web/templates/topic/_node_selector.html.eex:12\n#: lib/mipha_web/templates/topic/_nodes.html.eex:2\nmsgid \"Nodes\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:11\nmsgid \"Normal\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:79\nmsgid \"Normal topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:7\nmsgid \"Note\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:8\nmsgid \"Note! You will need to log in again after updating your password.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/notification/index.html.eex:3\nmsgid \"Notifications\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:65\nmsgid \"Opened\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:44\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:32\nmsgid \"Pin\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:13\nmsgid \"Popular\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_editor_toolbar.html.eex:34\nmsgid \"Preview\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:6\nmsgid \"Profile\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:43\nmsgid \"Profile updated failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:38\nmsgid \"Profile updated successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/show.html.eex:22\nmsgid \"Recent Repleis\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/session_controller.ex:48\nmsgid \"Register failed. pls try again.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/session_controller.ex:62\nmsgid \"Registered successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:18\nmsgid \"Remember me (2 months)\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:45\nmsgid \"Replied to\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:49\nmsgid \"Replies Count: %{reply_count}\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/topics.html.eex:14\nmsgid \"Replies/Hits\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_handle_reply.html.eex:5\n#: lib/mipha_web/templates/user/_menu.html.eex:4\nmsgid \"Reply\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:61\nmsgid \"Reply deleted successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:51\nmsgid \"Reply updated error.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:46\nmsgid \"Reply updated successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/reset_password.html.eex:5\nmsgid \"Reset Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:179\nmsgid \"Reset password successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:24\n#: lib/mipha_web/templates/user/_left.html.eex:81\nmsgid \"Reward\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/reply/edit.html.eex:17\nmsgid \"Save\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:30\nmsgid \"Setting\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:5\n#: lib/mipha_web/templates/auth/login.html.eex:23\n#: lib/mipha_web/templates/layout/_header.html.eex:71\nmsgid \"Sign In\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:66\nmsgid \"Sign Out\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:42\n#: lib/mipha_web/templates/layout/_header.html.eex:70\n#: lib/mipha_web/templates/session/new.html.eex:5\n#: lib/mipha_web/templates/session/new.html.eex:33\nmsgid \"Register\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:32\nmsgid \"Sign in with other services\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:15\nmsgid \"Sign out successful\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:19\nmsgid \"Source Code\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:79\n#: lib/mipha_web/controllers/topic_controller.ex:197\nmsgid \"Star failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:74\n#: lib/mipha_web/controllers/topic_controller.ex:192\nmsgid \"Star successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:45\nmsgid \"Statistics\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_form.html.eex:25\n#: lib/mipha_web/templates/topic/_handle_reply.html.eex:19\nmsgid \"Submit\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/reset_password.html.eex:18\nmsgid \"Sumbit\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/index.html.eex:4\nmsgid \"TOP 100 active users\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:33\nmsgid \"Tagline\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/team/_nav.html.eex:6\nmsgid \"Team People\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/team/_nav.html.eex:3\nmsgid \"Team Topics\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:142\nmsgid \"The email is invalid.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:167\nmsgid \"The verification link is invalid.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/collections.html.eex:13\n#: lib/mipha_web/templates/user/topics.html.eex:13\nmsgid \"Title\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:10\n#: lib/mipha_web/templates/user/_menu.html.eex:3\nmsgid \"Topic\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:130\nmsgid \"Topic created successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:175\nmsgid \"Topic deleted successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:146\nmsgid \"Topic updated successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:48\nmsgid \"Topics Count: %{topic_count}\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:3\nmsgid \"Total\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right_sidebar.html.eex:37\nmsgid \"Total %{topic_count} replies\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:63\n#: lib/mipha_web/templates/user/following.html.eex:29\nmsgid \"UnFollow\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:16\n#: lib/mipha_web/templates/topic/_right_sidebar.html.eex:24\nmsgid \"Uncollection\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:253\nmsgid \"Uncollection failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:248\nmsgid \"Uncollection successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/followers.html.eex:28\n#: lib/mipha_web/templates/user/index.html.eex:26\nmsgid \"Unfollow\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:116\nmsgid \"Unfollow failed.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:111\nmsgid \"Unfollow successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:38\nmsgid \"Unmark featured\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:51\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:28\nmsgid \"Unpin\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:98\n#: lib/mipha_web/controllers/topic_controller.ex:215\nmsgid \"Unstar failed\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:93\n#: lib/mipha_web/controllers/topic_controller.ex:211\nmsgid \"Unstar successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:38\n#: lib/mipha_web/templates/setting/show.html.eex:41\nmsgid \"Update\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:35\nmsgid \"Update Password\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/show.html.eex:25\nmsgid \"Upload avatar\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:16\nmsgid \"User\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:14\nmsgid \"User not exist.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:57\n#: lib/mipha_web/controllers/setting_controller.ex:101\nmsgid \"User updated failed.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:67\nmsgid \"User updated password successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:52\n#: lib/mipha_web/controllers/setting_controller.ex:96\nmsgid \"User updated successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:47\nmsgid \"Users Count: %{user_count}\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:28\nmsgid \"Website\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/forgot_password.html.eex:13\nmsgid \"Will send a special link to retrieve your password to your email address. You can enter the reset password page by changing the link.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:86\nmsgid \"You has logged in\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:137\nmsgid \"You will receive an email to reset your password.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/session_controller.ex:36\nmsgid \"captcha error, pls input again.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/show.html.eex:25\nmsgid \"hits\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_topic.html.eex:28\n#: lib/mipha_web/templates/topic/show.html.eex:20\nmsgid \"replied at\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:4\nmsgid \"replies\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/notification_controller.ex:36\nmsgid \"Cleaned notification successfully\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/notification_controller.ex:26\nmsgid \"Marked all notifications readed.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/show.html.eex:18\nmsgid \"Public Email\"\nmsgstr \"\"\n"
  },
  {
    "path": "priv/gettext/en/LC_MESSAGES/errors.po",
    "content": "## `msgid`s in this file come from POT (.pot) files.\n##\n## Do not add, change, or remove `msgid`s manually here as\n## they're tied to the ones in the corresponding POT file\n## (with the same domain).\n##\n## Use `mix gettext.extract --merge` or `mix gettext.merge`\n## to merge POT files into PO files.\nmsgid \"\"\nmsgstr \"\"\n\"Language: en\\n\"\n\n## From Ecto.Changeset.cast/4\nmsgid \"can't be blank\"\nmsgstr \"\"\n\n## From Ecto.Changeset.unique_constraint/3\nmsgid \"has already been taken\"\nmsgstr \"\"\n\n## From Ecto.Changeset.put_change/3\nmsgid \"is invalid\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_acceptance/3\nmsgid \"must be accepted\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_format/3\nmsgid \"has invalid format\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_subset/3\nmsgid \"has an invalid entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_exclusion/3\nmsgid \"is reserved\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_confirmation/3\nmsgid \"does not match confirmation\"\nmsgstr \"\"\n\n## From Ecto.Changeset.no_assoc_constraint/3\nmsgid \"is still associated with this entry\"\nmsgstr \"\"\n\nmsgid \"are still associated with this entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_length/3\nmsgid \"should be %{count} character(s)\"\nmsgid_plural \"should be %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have %{count} item(s)\"\nmsgid_plural \"should have %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at least %{count} character(s)\"\nmsgid_plural \"should be at least %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at least %{count} item(s)\"\nmsgid_plural \"should have at least %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at most %{count} character(s)\"\nmsgid_plural \"should be at most %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at most %{count} item(s)\"\nmsgid_plural \"should have at most %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n## From Ecto.Changeset.validate_number/3\nmsgid \"must be less than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be less than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be equal to %{number}\"\nmsgstr \"\"\n"
  },
  {
    "path": "priv/gettext/errors.pot",
    "content": "## This file is a PO Template file.\n##\n## `msgid`s here are often extracted from source code.\n## Add new translations manually only if they're dynamic\n## translations that can't be statically extracted.\n##\n## Run `mix gettext.extract` to bring this file up to\n## date. Leave `msgstr`s empty as changing them here as no\n## effect: edit them in PO (`.po`) files instead.\n## From Ecto.Changeset.cast/4\nmsgid \"can't be blank\"\nmsgstr \"\"\n\n## From Ecto.Changeset.unique_constraint/3\nmsgid \"has already been taken\"\nmsgstr \"\"\n\n## From Ecto.Changeset.put_change/3\nmsgid \"is invalid\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_acceptance/3\nmsgid \"must be accepted\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_format/3\nmsgid \"has invalid format\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_subset/3\nmsgid \"has an invalid entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_exclusion/3\nmsgid \"is reserved\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_confirmation/3\nmsgid \"does not match confirmation\"\nmsgstr \"\"\n\n## From Ecto.Changeset.no_assoc_constraint/3\nmsgid \"is still associated with this entry\"\nmsgstr \"\"\n\nmsgid \"are still associated with this entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_length/3\nmsgid \"should be %{count} character(s)\"\nmsgid_plural \"should be %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have %{count} item(s)\"\nmsgid_plural \"should have %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at least %{count} character(s)\"\nmsgid_plural \"should be at least %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at least %{count} item(s)\"\nmsgid_plural \"should have at least %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at most %{count} character(s)\"\nmsgid_plural \"should be at most %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at most %{count} item(s)\"\nmsgid_plural \"should have at most %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n## From Ecto.Changeset.validate_number/3\nmsgid \"must be less than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be less than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be equal to %{number}\"\nmsgstr \"\"\n"
  },
  {
    "path": "priv/gettext/zh/LC_MESSAGES/default.po",
    "content": "#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:5\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:10\n#: lib/mipha_web/templates/topic/_replies.html.eex:37\nmsgid \"%{star_count} stars\"\nmsgstr \"%{star_count} 个 赞\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:63\nmsgid \"Account Profile\"\nmsgstr \"个人资料设置\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:7\nmsgid \"All Nodes\"\nmsgstr \"所有节点\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:18\nmsgid \"Change Password\"\nmsgstr \"修改密码\"\n\n#, elixir-format\n#: lib/mipha_web/templates/notification/index.html.eex:6\nmsgid \"Clean all\"\nmsgstr \"清空全部\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:58\nmsgid \"Closed\"\nmsgstr \"关闭\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:20\n#: lib/mipha_web/templates/topic/_right_sidebar.html.eex:28\n#: lib/mipha_web/templates/user/_left.html.eex:54\n#: lib/mipha_web/templates/user/_menu.html.eex:5\nmsgid \"Collection\"\nmsgstr \"收藏\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:234\nmsgid \"Collection failed\"\nmsgstr \"收藏失败\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:229\nmsgid \"Collection successfully\"\nmsgstr \"已收藏成功\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:29\nmsgid \"Confirmation Password\"\nmsgstr \"重新密码\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:136\nmsgid \"Create topic failed, pls select node_id.\"\nmsgstr \"新建帖子失败，请选择分类节点\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_topic.html.eex:31\nmsgid \"Created at\"\nmsgstr \"发布于\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/index.html.eex:5\nmsgid \"Current have %{user_count} users joined Elixir Mipha\"\nmsgstr \"目前已经有 %{user_count} 位会员加入了 Elixir Mipha\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:17\nmsgid \"Default Password\"\nmsgstr \"原密码\"\n\n#, elixir-format\n#: lib/mipha_web/templates/reply/edit.html.eex:18\nmsgid \"Delete\"\nmsgstr \"删除\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:12\nmsgid \"Details\"\nmsgstr \"详细信息\"\n\n#, elixir-format\n#: lib/mipha_web/templates/reply/edit.html.eex:4\nmsgid \"Edit Reply\"\nmsgstr \"编辑回帖\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/edit.html.eex:3\nmsgid \"Edit Topic\"\nmsgstr \"编辑话题\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:26\nmsgid \"Edit reply\"\nmsgstr \"编辑回帖\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:29\nmsgid \"Failed to authenticate.\"\nmsgstr \"用户验证失败\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:12\nmsgid \"Featured\"\nmsgstr \"精华帖\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:72\nmsgid \"Featured topic\"\nmsgstr \"该话题设置为精华帖\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:68\n#: lib/mipha_web/templates/user/followers.html.eex:32\n#: lib/mipha_web/templates/user/following.html.eex:33\n#: lib/mipha_web/templates/user/index.html.eex:30\nmsgid \"Follow\"\nmsgstr \"关注\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:97\nmsgid \"Follow failed.\"\nmsgstr \"关注失败\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:92\nmsgid \"Follow successfully.\"\nmsgstr \"已关注成功\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:46\n#: lib/mipha_web/templates/user/_menu.html.eex:7\nmsgid \"Follower\"\nmsgstr \"关注者\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:50\n#: lib/mipha_web/templates/user/_menu.html.eex:6\nmsgid \"Following\"\nmsgstr \"正在关注\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/forgot_password.html.eex:5\nmsgid \"Forgot Password?\"\nmsgstr \"忘记密码？\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:43\nmsgid \"Forgot your password?\"\nmsgstr \"忘记密码？\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:14\nmsgid \"Friendship community\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:35\nmsgid \"Github\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/page/_locations.html.eex:3\nmsgid \"Hot Cities\"\nmsgstr \"热门城市\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/show.html.eex:17\nmsgid \"Hot Topics\"\nmsgstr \"热门话题\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_menu.html.eex:2\nmsgid \"Info\"\nmsgstr \"个人信息\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/show.html.eex:27\nmsgid \"Introduction\"\nmsgstr \"个人简介\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:72\nmsgid \"Invalid password\"\nmsgstr \"无效的密码\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:9\nmsgid \"It's open source project, for more information, please visit: \"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:13\nmsgid \"Job\"\nmsgstr \"招聘\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_topic.html.eex:24\n#: lib/mipha_web/templates/topic/show.html.eex:18\nmsgid \"Last by\"\nmsgstr \"最后由\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:13\nmsgid \"Location\"\nmsgstr \"地址\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:77\nmsgid \"Login successfully\"\nmsgstr \"登录成功\"\n\n#, elixir-format\n#: lib/mipha_web/templates/notification/index.html.eex:5\nmsgid \"Mark all readed\"\nmsgstr \"标记全部已读\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:42\nmsgid \"Mark featured\"\nmsgstr \"设置为精华帖\"\n\n#, elixir-format\n#: lib/mipha_web/templates/page/markdown.html.eex:3\n#: lib/mipha_web/templates/topic/_form.html.eex:28\n#: lib/mipha_web/templates/topic/_handle_reply.html.eex:27\nmsgid \"Markdown Guide\"\nmsgstr \"Markdown 排版说明\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:23\nmsgid \"New Password\"\nmsgstr \"新密码\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:52\n#: lib/mipha_web/templates/topic/_right.html.eex:3\n#: lib/mipha_web/templates/topic/new.html.eex:3\nmsgid \"New Topic\"\nmsgstr \"发布新话题\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:14\nmsgid \"No Reply\"\nmsgstr \"无人问津\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/collections.html.eex:12\n#: lib/mipha_web/templates/user/topics.html.eex:12\nmsgid \"Node\"\nmsgstr \"节点\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_node_selector.html.eex:5\n#: lib/mipha_web/templates/topic/_node_selector.html.eex:12\n#: lib/mipha_web/templates/topic/_nodes.html.eex:2\nmsgid \"Nodes\"\nmsgstr \"全部节点\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:11\nmsgid \"Normal\"\nmsgstr \"默认\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:79\nmsgid \"Normal topic\"\nmsgstr \"该话题设置为正常帖\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:7\nmsgid \"Note\"\nmsgstr \"小贴士\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:8\nmsgid \"Note! You will need to log in again after updating your password.\"\nmsgstr \"注意！更新密码以后需要重新登陆。\"\n\n#, elixir-format\n#: lib/mipha_web/templates/notification/index.html.eex:3\nmsgid \"Notifications\"\nmsgstr \"全部通知\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:65\nmsgid \"Opened\"\nmsgstr \"打开\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:44\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:32\nmsgid \"Pin\"\nmsgstr \"置顶\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_sub_header.html.eex:13\nmsgid \"Popular\"\nmsgstr \"优质帖\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_editor_toolbar.html.eex:34\nmsgid \"Preview\"\nmsgstr \"预览\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:6\nmsgid \"Profile\"\nmsgstr \"基本信息\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:43\nmsgid \"Profile updated failed\"\nmsgstr \"用户信息更新失败\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:38\nmsgid \"Profile updated successfully\"\nmsgstr \"用户信息已更新成功\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/show.html.eex:22\nmsgid \"Recent Repleis\"\nmsgstr \"最近回帖\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/session_controller.ex:48\nmsgid \"Register failed. pls try again.\"\nmsgstr \"注册失败，请重新注册\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/session_controller.ex:62\nmsgid \"Registered successfully\"\nmsgstr \"注册成功\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:18\nmsgid \"Remember me (2 months)\"\nmsgstr \"记住登录状态（60 天）\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:45\nmsgid \"Replied to\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:49\nmsgid \"Replies Count: %{reply_count}\"\nmsgstr \"回帖数: %{reply_count}\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/topics.html.eex:14\nmsgid \"Replies/Hits\"\nmsgstr \"回复数/访问数\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_handle_reply.html.eex:5\n#: lib/mipha_web/templates/user/_menu.html.eex:4\nmsgid \"Reply\"\nmsgstr \"评论\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:61\nmsgid \"Reply deleted successfully.\"\nmsgstr \"已成功删除回帖\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:51\nmsgid \"Reply updated error.\"\nmsgstr \"修改回帖失败\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:46\nmsgid \"Reply updated successfully.\"\nmsgstr \"已成功修改回帖\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/reset_password.html.eex:5\nmsgid \"Reset Password\"\nmsgstr \"重置用户密码\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:179\nmsgid \"Reset password successfully.\"\nmsgstr \"重置密码成功\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:24\n#: lib/mipha_web/templates/user/_left.html.eex:81\nmsgid \"Reward\"\nmsgstr \"打赏\"\n\n#, elixir-format\n#: lib/mipha_web/templates/reply/edit.html.eex:17\nmsgid \"Save\"\nmsgstr \"保存\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/_sidebar.html.eex:30\nmsgid \"Setting\"\nmsgstr \"账户设置\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:5\n#: lib/mipha_web/templates/auth/login.html.eex:23\n#: lib/mipha_web/templates/layout/_header.html.eex:71\nmsgid \"Sign In\"\nmsgstr \"登录\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:66\nmsgid \"Sign Out\"\nmsgstr \"退出\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:42\n#: lib/mipha_web/templates/layout/_header.html.eex:70\n#: lib/mipha_web/templates/session/new.html.eex:5\n#: lib/mipha_web/templates/session/new.html.eex:33\nmsgid \"Register\"\nmsgstr \"注册\"\n\n#, elixir-format\n#: lib/mipha_web/templates/auth/login.html.eex:32\nmsgid \"Sign in with other services\"\nmsgstr \"第三方登录\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:15\nmsgid \"Sign out successful\"\nmsgstr \"退出成功\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:19\nmsgid \"Source Code\"\nmsgstr \"开源代码\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:79\n#: lib/mipha_web/controllers/topic_controller.ex:197\nmsgid \"Star failed\"\nmsgstr \"点赞失败\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:74\n#: lib/mipha_web/controllers/topic_controller.ex:192\nmsgid \"Star successfully\"\nmsgstr \"点赞成功\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:45\nmsgid \"Statistics\"\nmsgstr \"统计信息\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_form.html.eex:25\n#: lib/mipha_web/templates/topic/_handle_reply.html.eex:19\nmsgid \"Submit\"\nmsgstr \"提交\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/reset_password.html.eex:18\nmsgid \"Sumbit\"\nmsgstr \"提交\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/index.html.eex:4\nmsgid \"TOP 100 active users\"\nmsgstr \"TOP 100 活跃会员\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:33\nmsgid \"Tagline\"\nmsgstr \"个性签名\"\n\n#, elixir-format\n#: lib/mipha_web/templates/team/_nav.html.eex:6\nmsgid \"Team People\"\nmsgstr \"团队成员\"\n\n#, elixir-format\n#: lib/mipha_web/templates/team/_nav.html.eex:3\nmsgid \"Team Topics\"\nmsgstr \"团队帖子\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:142\nmsgid \"The email is invalid.\"\nmsgstr \"该 Email 不存在\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:167\nmsgid \"The verification link is invalid.\"\nmsgstr \"无效的验证链接\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/collections.html.eex:13\n#: lib/mipha_web/templates/user/topics.html.eex:13\nmsgid \"Title\"\nmsgstr \"标题\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:10\n#: lib/mipha_web/templates/user/_menu.html.eex:3\nmsgid \"Topic\"\nmsgstr \"话题\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:130\nmsgid \"Topic created successfully.\"\nmsgstr \"帖子创建成功\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:175\nmsgid \"Topic deleted successfully.\"\nmsgstr \"帖子已删除\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:146\nmsgid \"Topic updated successfully.\"\nmsgstr \"帖子更新成功\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:48\nmsgid \"Topics Count: %{topic_count}\"\nmsgstr \"帖子数: %{topic_count}\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:3\nmsgid \"Total\"\nmsgstr \"总共\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right_sidebar.html.eex:37\nmsgid \"Total %{topic_count} replies\"\nmsgstr \"共 %{topic_count} 回复\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/_left.html.eex:63\n#: lib/mipha_web/templates/user/following.html.eex:29\nmsgid \"UnFollow\"\nmsgstr \"取消关注\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:16\n#: lib/mipha_web/templates/topic/_right_sidebar.html.eex:24\nmsgid \"Uncollection\"\nmsgstr \"取消收藏\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:253\nmsgid \"Uncollection failed\"\nmsgstr \"取消收藏操作失败\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:248\nmsgid \"Uncollection successfully\"\nmsgstr \"已取消收藏\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/followers.html.eex:28\n#: lib/mipha_web/templates/user/index.html.eex:26\nmsgid \"Unfollow\"\nmsgstr \"取消关注\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:116\nmsgid \"Unfollow failed.\"\nmsgstr \"取消关注失败\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:111\nmsgid \"Unfollow successfully.\"\nmsgstr \"已取消关注\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:38\nmsgid \"Unmark featured\"\nmsgstr \"取消精华帖\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/topic_controller.ex:51\n#: lib/mipha_web/templates/topic/_operate_toolbar.html.eex:28\nmsgid \"Unpin\"\nmsgstr \"取消置顶\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:98\n#: lib/mipha_web/controllers/topic_controller.ex:215\nmsgid \"Unstar failed\"\nmsgstr \"取消赞失败\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/reply_controller.ex:93\n#: lib/mipha_web/controllers/topic_controller.ex:211\nmsgid \"Unstar successfully\"\nmsgstr \"取消赞\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:38\n#: lib/mipha_web/templates/setting/show.html.eex:41\nmsgid \"Update\"\nmsgstr \"更新\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/password.html.eex:35\nmsgid \"Update Password\"\nmsgstr \"修改密码\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/show.html.eex:25\nmsgid \"Upload avatar\"\nmsgstr \"上传头像\"\n\n#, elixir-format\n#: lib/mipha_web/templates/layout/_header.html.eex:16\nmsgid \"User\"\nmsgstr \"用户\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:14\nmsgid \"User not exist.\"\nmsgstr \"该用户不存在\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:57\n#: lib/mipha_web/controllers/setting_controller.ex:101\nmsgid \"User updated failed.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:67\nmsgid \"User updated password successfully.\"\nmsgstr \"密码更新成功\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/setting_controller.ex:52\n#: lib/mipha_web/controllers/setting_controller.ex:96\nmsgid \"User updated successfully.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_right.html.eex:47\nmsgid \"Users Count: %{user_count}\"\nmsgstr \"用户数: %{user_count}\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/profile.html.eex:28\nmsgid \"Website\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/user/forgot_password.html.eex:13\nmsgid \"Will send a special link to retrieve your password to your email address. You can enter the reset password page by changing the link.\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/auth_controller.ex:86\nmsgid \"You has logged in\"\nmsgstr \"你已登录\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/user_controller.ex:137\nmsgid \"You will receive an email to reset your password.\"\nmsgstr \"稍后，您将收到重置密码的电子邮件。\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/session_controller.ex:36\nmsgid \"captcha error, pls input again.\"\nmsgstr \"验证码错误，请重新输入\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/show.html.eex:25\nmsgid \"hits\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_topic.html.eex:28\n#: lib/mipha_web/templates/topic/show.html.eex:20\nmsgid \"replied at\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/templates/topic/_replies.html.eex:4\nmsgid \"replies\"\nmsgstr \"\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/notification_controller.ex:36\nmsgid \"Cleaned notification successfully\"\nmsgstr \"已清空站内信\"\n\n#, elixir-format\n#: lib/mipha_web/controllers/notification_controller.ex:26\nmsgid \"Marked all notifications readed.\"\nmsgstr \"已全部标记为已读\"\n\n#, elixir-format\n#: lib/mipha_web/templates/setting/show.html.eex:18\nmsgid \"Public Email\"\nmsgstr \"公开邮箱\"\n"
  },
  {
    "path": "priv/gettext/zh/LC_MESSAGES/errors.po",
    "content": "## `msgid`s in this file come from POT (.pot) files.\n##\n## Do not add, change, or remove `msgid`s manually here as\n## they're tied to the ones in the corresponding POT file\n## (with the same domain).\n##\n## Use `mix gettext.extract --merge` or `mix gettext.merge`\n## to merge POT files into PO files.\nmsgid \"\"\nmsgstr \"\"\n\"Language: zh\\n\"\n\"Plural-Forms: nplurals=1\\n\"\n\nmsgid \"can't be blank\"\nmsgstr \"\"\n\nmsgid \"has already been taken\"\nmsgstr \"\"\n\nmsgid \"is invalid\"\nmsgstr \"\"\n\nmsgid \"must be accepted\"\nmsgstr \"\"\n\nmsgid \"has invalid format\"\nmsgstr \"\"\n\nmsgid \"has an invalid entry\"\nmsgstr \"\"\n\nmsgid \"is reserved\"\nmsgstr \"\"\n\nmsgid \"does not match confirmation\"\nmsgstr \"\"\n\nmsgid \"is still associated with this entry\"\nmsgstr \"\"\n\nmsgid \"are still associated with this entry\"\nmsgstr \"\"\n\nmsgid \"should be %{count} character(s)\"\nmsgid_plural \"should be %{count} character(s)\"\nmsgstr[0] \"\"\n\nmsgid \"should have %{count} item(s)\"\nmsgid_plural \"should have %{count} item(s)\"\nmsgstr[0] \"\"\n\nmsgid \"should be at least %{count} character(s)\"\nmsgid_plural \"should be at least %{count} character(s)\"\nmsgstr[0] \"\"\n\nmsgid \"should have at least %{count} item(s)\"\nmsgid_plural \"should have at least %{count} item(s)\"\nmsgstr[0] \"\"\n\nmsgid \"should be at most %{count} character(s)\"\nmsgid_plural \"should be at most %{count} character(s)\"\nmsgstr[0] \"\"\n\nmsgid \"should have at most %{count} item(s)\"\nmsgid_plural \"should have at most %{count} item(s)\"\nmsgstr[0] \"\"\n\nmsgid \"must be less than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be less than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be equal to %{number}\"\nmsgstr \"\"\n"
  },
  {
    "path": "priv/repo/migrations/20180629095811_create_users.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateUsers do\n  use Ecto.Migration\n\n  def change do\n    create table(:users) do\n      add(:username, :string)\n      add(:email, :string)\n      add(:email_public, :boolean)\n      add(:email_verified_at, :naive_datetime)\n      add(:password_hash, :string)\n      add(:avatar, :string)\n      add(:bio, :string)\n      add(:tagline, :string)\n      add(:website, :string)\n      add(:github_handle, :string)\n      add(:is_admin, :boolean, default: false, null: false)\n      add(:location_id, :integer)\n      add(:company_id, :integer)\n      add(:wechat, :string)\n      add(:alipay, :string)\n      add(:locked_at, :naive_datetime)\n\n      timestamps()\n    end\n\n    create(unique_index(:users, [:username]))\n    create(unique_index(:users, [:email]))\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180702075249_create_topics.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateTopics do\n  use Ecto.Migration\n\n  def change do\n    create table(:topics) do\n      add(:title, :string)\n      add(:body, :text)\n      add(:closed_at, :naive_datetime)\n      add(:user_id, :integer)\n      add(:type, :string)\n      add(:node_id, :integer)\n      add(:visit_count, :integer)\n      add(:reply_count, :integer)\n      add(:last_reply_id, :integer)\n      add(:last_reply_user_id, :integer)\n      add(:replied_at, :naive_datetime)\n      add(:suggested_at, :naive_datetime)\n      add(:star_count, :integer)\n\n      timestamps()\n    end\n\n    create(index(:topics, [:user_id]))\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180702081217_create_replies.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateReplies do\n  use Ecto.Migration\n\n  def change do\n    create table(:replies) do\n      add(:topic_id, :integer)\n      add(:user_id, :integer)\n      add(:parent_id, :integer)\n      add(:content, :text)\n      add(:star_count, :integer)\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180702081845_create_nodes.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateNodes do\n  use Ecto.Migration\n\n  def change do\n    create table(:nodes) do\n      add(:name, :string)\n      add(:summary, :string)\n      add(:position, :integer)\n      add(:parent_id, :integer)\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180703094024_create_locations.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateLocations do\n  use Ecto.Migration\n\n  def change do\n    create table(:locations) do\n      add(:name, :string)\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180704024042_create_collections.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateCollections do\n  use Ecto.Migration\n\n  def change do\n    create table(:collections) do\n      add(:user_id, :integer)\n      add(:topic_id, :integer)\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180704024600_create_follows.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateFollows do\n  use Ecto.Migration\n\n  def change do\n    create table(:follows) do\n      add(:user_id, :integer)\n      add(:follower_id, :integer)\n\n      timestamps()\n    end\n\n    create(unique_index(:follows, [:follower_id, :user_id]))\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180704025308_create_stars.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateStars do\n  use Ecto.Migration\n\n  def change do\n    create table(:stars) do\n      add(:user_id, :integer)\n      add(:reply_id, :integer)\n      add(:topic_id, :integer)\n\n      timestamps()\n    end\n\n    create(unique_index(:stars, [:user_id, :topic_id]))\n    create(unique_index(:stars, [:user_id, :reply_id]))\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180704032212_create_companies.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateCompanies do\n  use Ecto.Migration\n\n  def change do\n    create table(:companies) do\n      add(:name, :string)\n      add(:avatar, :string)\n      add(:location_id, :integer)\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180704053120_create_teams.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateTeams do\n  use Ecto.Migration\n\n  def change do\n    create table(:teams) do\n      add(:owner_id, :integer)\n      add(:github_handle, :string)\n      add(:name, :string)\n      add(:summary, :string)\n      add(:avatar, :string)\n      add(:email, :string)\n      add(:email_public, :boolean)\n      add(:twitter_handle, :string)\n      add(:slug, :string)\n      add(:website, :string)\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180704053159_create_users_teams.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateUsersTeams do\n  use Ecto.Migration\n\n  def change do\n    create table(:users_teams) do\n      add(:user_id, :integer)\n      add(:team_id, :integer)\n      add(:role, :string)\n      add(:status, :string)\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180712015550_create_notifications.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateNotifications do\n  use Ecto.Migration\n\n  def change do\n    create table(:notifications) do\n      add(:action, :string)\n      add(:actor_id, :integer)\n      add(:reply_id, :integer)\n      add(:user_id, :integer)\n      add(:topic_id, :integer)\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/migrations/20180712015614_create_users_notifications.exs",
    "content": "defmodule Mipha.Repo.Migrations.CreateUsersNotifications do\n  use Ecto.Migration\n\n  def change do\n    create table(:users_notifications) do\n      add(:user_id, :integer)\n      add(:notification_id, :integer)\n      add(:read_at, :naive_datetime)\n\n      timestamps()\n    end\n  end\nend\n"
  },
  {
    "path": "priv/repo/seeds.exs",
    "content": "alias Mipha.{\n  Repo,\n  Accounts,\n  Topics\n}\n\nalias Accounts.{User, Location, Company, Team}\nalias Topics.{Topic, Node}\n\n# Gen three locations\nbeijing = Repo.insert!(%Location{name: \"北京\"})\nhangzhou = Repo.insert!(%Location{name: \"杭州\"})\n\nhelijia =\n  Repo.insert!(%Company{\n    name: \"河狸家\",\n    location: beijing\n  })\n\nqhwa =\n  User.register_changeset(%User{}, %{\n    username: \"qhwa\",\n    email: \"qhwa@mipha.com\",\n    password: \"123123123\",\n    is_admin: true,\n    bio: Faker.Lorem.sentence(10),\n    website: Faker.Internet.domain_name(),\n    github_handle: \"qhwa\",\n    location_id: beijing.id,\n    company_id: helijia.id\n  })\n  |> Repo.insert!()\n\nzven =\n  User.register_changeset(%User{}, %{\n    username: \"zven\",\n    email: \"zven@mipha.com\",\n    password: \"123123123\",\n    bio: Faker.Lorem.sentence(10),\n    website: Faker.Internet.domain_name(),\n    github_handle: \"zven21\",\n    location_id: beijing.id,\n    company_id: helijia.id\n  })\n  |> Repo.insert!()\n\nbencode =\n  User.register_changeset(%User{}, %{\n    username: \"bencode\",\n    email: \"bencode@mipha.com\",\n    password: \"123123123\",\n    bio: Faker.Lorem.sentence(10),\n    website: Faker.Internet.domain_name(),\n    github_handle: \"bencode\",\n    location_id: hangzhou.id\n  })\n  |> Repo.insert!()\n\n# Gen teams helijia_web\nRepo.insert!(%Team{\n  name: \"helijia-web\",\n  summary: Faker.Lorem.sentence(10),\n  owner: zven,\n  github_handle: \"helijia-web\",\n  avatar: Faker.Avatar.image_url(),\n  users: [qhwa, zven, bencode]\n})\n\nfor parent_node <- ~w(ruby elixir erlang) do\n  node =\n    Repo.insert!(%Node{\n      name: parent_node,\n      summary: Faker.Lorem.sentence(10),\n      position: 1..10 |> Enum.random()\n    })\n\n  for idx <- 1..3 do\n    Repo.insert!(%Node{\n      name: \"#{node.name}-node#{idx}\",\n      summary: Faker.Lorem.sentence(10),\n      parent: node,\n      position: 1..10 |> Enum.random()\n    })\n  end\nend\n\nfor _ <- 1..10 do\n  sample_user = Repo.all(User) |> Enum.random()\n  sample_node = Repo.all(Node.is_child()) |> Enum.random()\n\n  Repo.insert!(%Topic{\n    title: Faker.Lorem.sentence(10),\n    body: Faker.Lorem.sentence(10),\n    last_reply_user: sample_user,\n    node: sample_node,\n    type: Enum.random(~w(normal featured educational job)),\n    user: Repo.all(User) |> Enum.random()\n  })\nend\n"
  },
  {
    "path": "script/credo",
    "content": "#!/bin/bash -ex\n\nmix credo --strict\n"
  },
  {
    "path": "script/iex",
    "content": "#!/bin/bash -ex\n\niex --erl \"-kernel shell_history enabled\" -S mix\n"
  },
  {
    "path": "script/serve",
    "content": "#!/bin/bash -ex\n\niex --erl \"-kernel shell_history enabled\" -S mix phx.server\n"
  },
  {
    "path": "script/setup",
    "content": "#!/bin/bash -ex\n\nfor pkg in elixir node yarn; do\n  if brew list -1 | grep -q \"^${pkg}\\$\"; then\n    echo \"Package '$pkg' is installed, skipping..\"\n  else\n    echo \"brew install $pkg\"\n    brew install $pkg\n  fi\ndone\n\nset -x\n\nmix local.hex --force\nmix deps.get\nyarn --cwd=\"assets\" install"
  },
  {
    "path": "test/mipha/accounts/accounts_test.exs",
    "content": "defmodule Mipha.AccountsTest do\n  use Mipha.DataCase\n\n  alias Mipha.{Accounts, Repo}\n\n  describe \"users\" do\n    alias Mipha.Accounts.User\n\n    @valid_attrs %{\n      avatar: \"some avatar\",\n      bio: \"some bio\",\n      email: \"some email\",\n      github_handle: \"some github_handle\",\n      is_admin: true,\n      password_hash: \"some password_hash\",\n      username: \"some username\",\n      website: \"some website\"\n    }\n    @update_attrs %{\n      avatar: \"some updated avatar\",\n      bio: \"some updated bio\",\n      email: \"some updated email\",\n      github_handle: \"some updated github_handle\",\n      is_admin: false,\n      password_hash: \"some updated password_hash\",\n      username: \"some updated username\",\n      website: \"some updated website\"\n    }\n    @invalid_attrs %{\n      avatar: nil,\n      bio: nil,\n      email: nil,\n      github_handle: nil,\n      is_admin: nil,\n      password_hash: nil,\n      username: nil,\n      website: nil\n    }\n\n    def user_fixture(attrs \\\\ %{}) do\n      {:ok, user} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Accounts.create_user()\n\n      user\n    end\n\n    test \"get_user!/1 returns the user with given id\" do\n      user = user_fixture()\n      assert Accounts.get_user!(user.id) == user\n    end\n\n    test \"create_user/1 with valid data creates a user\" do\n      assert {:ok, %User{} = user} = Accounts.create_user(@valid_attrs)\n      assert user.avatar == \"some avatar\"\n      assert user.bio == \"some bio\"\n      assert user.email == \"some email\"\n      assert user.github_handle == \"some github_handle\"\n      assert user.is_admin == true\n      assert user.username == \"some username\"\n      assert user.website == \"some website\"\n    end\n\n    test \"create_user/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Accounts.create_user(@invalid_attrs)\n    end\n\n    test \"update_user/2 with valid data updates the user\" do\n      user = user_fixture()\n      assert {:ok, user} = Accounts.update_user(user, @update_attrs)\n      assert %User{} = user\n      assert user.avatar == \"some updated avatar\"\n      assert user.bio == \"some updated bio\"\n      assert user.email == \"some updated email\"\n      assert user.github_handle == \"some updated github_handle\"\n      assert user.is_admin == false\n      assert user.username == \"some updated username\"\n      assert user.website == \"some updated website\"\n    end\n\n    test \"update_user/2 with invalid data returns error changeset\" do\n      user = user_fixture()\n      assert {:error, %Ecto.Changeset{}} = Accounts.update_user(user, @invalid_attrs)\n      assert user == Accounts.get_user!(user.id)\n    end\n\n    test \"delete_user/1 deletes the user\" do\n      user = user_fixture()\n      assert {:ok, %User{}} = Accounts.delete_user(user)\n      assert_raise Ecto.NoResultsError, fn -> Accounts.get_user!(user.id) end\n    end\n\n    test \"change_user/1 returns a user changeset\" do\n      user = user_fixture()\n      assert %Ecto.Changeset{} = Accounts.change_user(user)\n    end\n  end\n\n  describe \"locations\" do\n    alias Mipha.Accounts.Location\n\n    @valid_attrs %{name: \"some name\"}\n    @update_attrs %{name: \"some updated name\"}\n    @invalid_attrs %{name: nil}\n\n    def location_fixture(attrs \\\\ %{}) do\n      {:ok, location} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Accounts.create_location()\n\n      location\n    end\n\n    test \"list_locations/0 returns all locations\" do\n      location = location_fixture()\n      assert Accounts.list_locations() == [location]\n    end\n\n    test \"get_location!/1 returns the location with given id\" do\n      location = location_fixture()\n      assert Accounts.get_location!(location.id) == location |> Repo.preload([:users])\n    end\n\n    test \"create_location/1 with valid data creates a location\" do\n      assert {:ok, %Location{} = location} = Accounts.create_location(@valid_attrs)\n      assert location.name == \"some name\"\n    end\n\n    test \"create_location/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Accounts.create_location(@invalid_attrs)\n    end\n\n    test \"update_location/2 with valid data updates the location\" do\n      location = location_fixture()\n      assert {:ok, location} = Accounts.update_location(location, @update_attrs)\n      assert %Location{} = location\n      assert location.name == \"some updated name\"\n    end\n\n    test \"update_location/2 with invalid data returns error changeset\" do\n      location = location_fixture()\n      assert {:error, %Ecto.Changeset{}} = Accounts.update_location(location, @invalid_attrs)\n      assert location |> Repo.preload([:users]) == Accounts.get_location!(location.id)\n    end\n\n    test \"delete_location/1 deletes the location\" do\n      location = location_fixture()\n      assert {:ok, %Location{}} = Accounts.delete_location(location)\n      assert_raise Ecto.NoResultsError, fn -> Accounts.get_location!(location.id) end\n    end\n\n    test \"change_location/1 returns a location changeset\" do\n      location = location_fixture()\n      assert %Ecto.Changeset{} = Accounts.change_location(location)\n    end\n  end\n\n  describe \"companies\" do\n    alias Mipha.Accounts.Company\n\n    @valid_attrs %{location_id: 42, name: \"some name\"}\n    @update_attrs %{location_id: 43, name: \"some updated name\"}\n    @invalid_attrs %{location_id: nil, name: nil}\n\n    def company_fixture(attrs \\\\ %{}) do\n      {:ok, company} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Accounts.create_company()\n\n      company\n    end\n\n    test \"get_company!/1 returns the company with given id\" do\n      company = company_fixture()\n      assert Accounts.get_company!(company.id) == company\n    end\n\n    test \"create_company/1 with valid data creates a company\" do\n      assert {:ok, %Company{} = company} = Accounts.create_company(@valid_attrs)\n      assert company.location_id == 42\n      assert company.name == \"some name\"\n    end\n\n    test \"create_company/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Accounts.create_company(@invalid_attrs)\n    end\n\n    test \"update_company/2 with valid data updates the company\" do\n      company = company_fixture()\n      assert {:ok, company} = Accounts.update_company(company, @update_attrs)\n      assert %Company{} = company\n      assert company.location_id == 43\n      assert company.name == \"some updated name\"\n    end\n\n    test \"update_company/2 with invalid data returns error changeset\" do\n      company = company_fixture()\n      assert {:error, %Ecto.Changeset{}} = Accounts.update_company(company, @invalid_attrs)\n      assert company == Accounts.get_company!(company.id)\n    end\n\n    test \"delete_company/1 deletes the company\" do\n      company = company_fixture()\n      assert {:ok, %Company{}} = Accounts.delete_company(company)\n      assert_raise Ecto.NoResultsError, fn -> Accounts.get_company!(company.id) end\n    end\n\n    test \"change_company/1 returns a company changeset\" do\n      company = company_fixture()\n      assert %Ecto.Changeset{} = Accounts.change_company(company)\n    end\n  end\n\n  describe \"teams\" do\n    alias Mipha.Accounts.Team\n\n    @valid_attrs %{\n      avatar: \"some avatar\",\n      github_handle: \"some github_handle\",\n      name: \"some name\",\n      owner_id: 42,\n      summary: \"some summary\"\n    }\n    @update_attrs %{\n      avatar: \"some updated avatar\",\n      github_handle: \"some updated github_handle\",\n      name: \"some updated name\",\n      owner_id: 43,\n      summary: \"some updated summary\"\n    }\n    @invalid_attrs %{avatar: nil, github_handle: nil, name: nil, owner_id: nil, summary: nil}\n\n    def team_fixture(attrs \\\\ %{}) do\n      {:ok, team} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Accounts.create_team()\n\n      team\n    end\n\n    test \"list_teams/0 returns all teams\" do\n      team = team_fixture()\n      assert Accounts.list_teams() == [team]\n    end\n\n    test \"get_team!/1 returns the team with given id\" do\n      team = team_fixture()\n      assert Accounts.get_team!(team.id) == team |> Repo.preload([:users, :owner])\n    end\n\n    test \"create_team/1 with valid data creates a team\" do\n      assert {:ok, %Team{} = team} = Accounts.create_team(@valid_attrs)\n      assert team.avatar == \"some avatar\"\n      assert team.github_handle == \"some github_handle\"\n      assert team.name == \"some name\"\n      assert team.owner_id == 42\n      assert team.summary == \"some summary\"\n    end\n\n    test \"create_team/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Accounts.create_team(@invalid_attrs)\n    end\n\n    test \"update_team/2 with valid data updates the team\" do\n      team = team_fixture()\n      assert {:ok, team} = Accounts.update_team(team, @update_attrs)\n      assert %Team{} = team\n      assert team.avatar == \"some updated avatar\"\n      assert team.github_handle == \"some updated github_handle\"\n      assert team.name == \"some updated name\"\n      assert team.owner_id == 43\n      assert team.summary == \"some updated summary\"\n    end\n\n    test \"update_team/2 with invalid data returns error changeset\" do\n      team = team_fixture()\n      assert {:error, %Ecto.Changeset{}} = Accounts.update_team(team, @invalid_attrs)\n      assert team |> Repo.preload([:users, :owner]) == Accounts.get_team!(team.id)\n    end\n\n    test \"delete_team/1 deletes the team\" do\n      team = team_fixture()\n      assert {:ok, %Team{}} = Accounts.delete_team(team)\n      assert_raise Ecto.NoResultsError, fn -> Accounts.get_team!(team.id) end\n    end\n\n    test \"change_team/1 returns a team changeset\" do\n      team = team_fixture()\n      assert %Ecto.Changeset{} = Accounts.change_team(team)\n    end\n  end\n\n  describe \"users_teams\" do\n    alias Mipha.Accounts.UserTeam\n\n    @valid_attrs %{team_id: 42, user_id: 42}\n    @update_attrs %{team_id: 43, user_id: 43}\n    @invalid_attrs %{team_id: nil, user_id: nil}\n\n    def user_team_fixture(attrs \\\\ %{}) do\n      {:ok, user_team} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Accounts.create_user_team()\n\n      user_team\n    end\n\n    test \"list_users_teams/0 returns all users_teams\" do\n      user_team = user_team_fixture()\n      assert Accounts.list_users_teams() == [user_team]\n    end\n\n    test \"get_user_team!/1 returns the user_team with given id\" do\n      user_team = user_team_fixture()\n      assert Accounts.get_user_team!(user_team.id) == user_team\n    end\n\n    test \"create_user_team/1 with valid data creates a user_team\" do\n      assert {:ok, %UserTeam{} = user_team} = Accounts.create_user_team(@valid_attrs)\n      assert user_team.team_id == 42\n      assert user_team.user_id == 42\n    end\n\n    test \"create_user_team/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Accounts.create_user_team(@invalid_attrs)\n    end\n\n    test \"update_user_team/2 with valid data updates the user_team\" do\n      user_team = user_team_fixture()\n      assert {:ok, user_team} = Accounts.update_user_team(user_team, @update_attrs)\n      assert %UserTeam{} = user_team\n      assert user_team.team_id == 43\n      assert user_team.user_id == 43\n    end\n\n    test \"update_user_team/2 with invalid data returns error changeset\" do\n      user_team = user_team_fixture()\n      assert {:error, %Ecto.Changeset{}} = Accounts.update_user_team(user_team, @invalid_attrs)\n      assert user_team == Accounts.get_user_team!(user_team.id)\n    end\n\n    test \"delete_user_team/1 deletes the user_team\" do\n      user_team = user_team_fixture()\n      assert {:ok, %UserTeam{}} = Accounts.delete_user_team(user_team)\n      assert_raise Ecto.NoResultsError, fn -> Accounts.get_user_team!(user_team.id) end\n    end\n\n    test \"change_user_team/1 returns a user_team changeset\" do\n      user_team = user_team_fixture()\n      assert %Ecto.Changeset{} = Accounts.change_user_team(user_team)\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/accounts/company_test.exs",
    "content": "defmodule Mipha.Accounts.AccountTest do\n  use Mipha.DataCase\n\n  alias Mipha.Accounts.Company\n\n  @valid_attrs %{name: \"some-company\", location_id: 1}\n  @invalid_attrs %{name: \"some-company\", location_id: nil}\n\n  describe \"changeset\" do\n    test \"with valid attrs\" do\n      changeset = Company.changeset(%Company{}, @valid_attrs)\n      assert changeset.valid?\n    end\n\n    test \"with invalid attrs\" do\n      changeset = Company.changeset(%Company{}, @invalid_attrs)\n      refute changeset.valid?\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/accounts/location_test.exs",
    "content": "defmodule Mipha.Accounts.LocationTest do\n  use Mipha.DataCase\n\n  alias Mipha.Accounts.Location\n\n  @valid_attrs %{name: \"some-location\"}\n  @invalid_attrs %{name: nil}\n\n  describe \"changeset\" do\n    test \"with valid attrs\" do\n      changeset = Location.changeset(%Location{}, @valid_attrs)\n      assert changeset.valid?\n    end\n\n    test \"with invalid attrs\" do\n      changeset = Location.changeset(%Location{}, @invalid_attrs)\n      refute changeset.valid?\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/accounts/queries_test.exs",
    "content": "defmodule Mipha.Accounts.QueriesTest do\n  use Mipha.DataCase\nend\n"
  },
  {
    "path": "test/mipha/accounts/user_test.exs",
    "content": "defmodule Mipha.Accounts.UserTest do\n  use Mipha.DataCase\n\n  alias Mipha.Accounts.User\n\n  @valid_attrs %{username: \"some-username\", email: \"zven21@mipha.com\"}\n  @invalid_attrs %{username: nil, email: nil}\n\n  describe \"changeset\" do\n    test \"with valid attrs\" do\n      changeset = User.changeset(%User{}, @valid_attrs)\n      assert changeset.valid?\n    end\n\n    test \"with invalid attrs\" do\n      changeset = User.changeset(%User{}, @invalid_attrs)\n      refute changeset.valid?\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/collections/collection_test.exs",
    "content": "defmodule Mipha.Collections.CollectionTest do\n  use Mipha.DataCase\n\n  describe \"changeset\" do\n    test \"with valid attrs\" do\n    end\n\n    test \"with invalid attrs\" do\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/collections/collections_test.exs",
    "content": "defmodule Mipha.CollectionsTest do\n  use Mipha.DataCase\n\n  alias Mipha.Collections\n\n  describe \"collections\" do\n    alias Mipha.Collections.Collection\n\n    @valid_attrs %{topic_id: 42, user_id: 42}\n    @update_attrs %{topic_id: 43, user_id: 43}\n    @invalid_attrs %{topic_id: nil, user_id: nil}\n\n    def collection_fixture(attrs \\\\ %{}) do\n      {:ok, collection} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Collections.create_collection()\n\n      collection\n    end\n\n    test \"list_collections/0 returns all collections\" do\n      collection = collection_fixture()\n      assert Collections.list_collections() == [collection]\n    end\n\n    test \"get_collection!/1 returns the collection with given id\" do\n      collection = collection_fixture()\n      assert Collections.get_collection!(collection.id) == collection\n    end\n\n    test \"create_collection/1 with valid data creates a collection\" do\n      assert {:ok, %Collection{} = collection} = Collections.create_collection(@valid_attrs)\n      assert collection.topic_id == 42\n      assert collection.user_id == 42\n    end\n\n    test \"create_collection/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Collections.create_collection(@invalid_attrs)\n    end\n\n    test \"update_collection/2 with valid data updates the collection\" do\n      collection = collection_fixture()\n      assert {:ok, collection} = Collections.update_collection(collection, @update_attrs)\n      assert %Collection{} = collection\n      assert collection.topic_id == 43\n      assert collection.user_id == 43\n    end\n\n    test \"update_collection/2 with invalid data returns error changeset\" do\n      collection = collection_fixture()\n\n      assert {:error, %Ecto.Changeset{}} =\n               Collections.update_collection(collection, @invalid_attrs)\n\n      assert collection == Collections.get_collection!(collection.id)\n    end\n\n    test \"delete_collection/1 deletes the collection\" do\n      collection = collection_fixture()\n      assert {:ok, %Collection{}} = Collections.delete_collection(collection)\n      assert_raise Ecto.NoResultsError, fn -> Collections.get_collection!(collection.id) end\n    end\n\n    test \"change_collection/1 returns a collection changeset\" do\n      collection = collection_fixture()\n      assert %Ecto.Changeset{} = Collections.change_collection(collection)\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/collections/queries_test.exs",
    "content": "defmodule Mipha.Collections.QueriesTest do\n  use Mipha.DataCase\nend\n"
  },
  {
    "path": "test/mipha/follows/follow_test.exs",
    "content": "defmodule Mipha.Follows.FollowTest do\n  use Mipha.DataCase\n\n  describe \"changeset\" do\n    test \"with valid attrs\" do\n    end\n\n    test \"with invalid attrs\" do\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/follows/follows_test.exs",
    "content": "defmodule Mipha.FollowsTest do\n  use Mipha.DataCase\n\n  alias Mipha.Follows\n\n  describe \"follows\" do\n    alias Mipha.Follows.Follow\n\n    @valid_attrs %{follower_id: 42, user_id: 42}\n    @update_attrs %{follower_id: 43, user_id: 43}\n    @invalid_attrs %{follower_id: nil, user_id: nil}\n\n    def follow_fixture(attrs \\\\ %{}) do\n      {:ok, follow} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Follows.create_follow()\n\n      follow\n    end\n\n    test \"list_follows/0 returns all follows\" do\n      follow = follow_fixture()\n      assert Follows.list_follows() == [follow]\n    end\n\n    test \"get_follow!/1 returns the follow with given id\" do\n      follow = follow_fixture()\n      assert Follows.get_follow!(follow.id) == follow\n    end\n\n    test \"create_follow/1 with valid data creates a follow\" do\n      assert {:ok, %Follow{} = follow} = Follows.create_follow(@valid_attrs)\n      assert follow.follower_id == 42\n      assert follow.user_id == 42\n    end\n\n    test \"create_follow/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Follows.create_follow(@invalid_attrs)\n    end\n\n    test \"update_follow/2 with valid data updates the follow\" do\n      follow = follow_fixture()\n      assert {:ok, follow} = Follows.update_follow(follow, @update_attrs)\n      assert %Follow{} = follow\n      assert follow.follower_id == 43\n      assert follow.user_id == 43\n    end\n\n    test \"update_follow/2 with invalid data returns error changeset\" do\n      follow = follow_fixture()\n      assert {:error, %Ecto.Changeset{}} = Follows.update_follow(follow, @invalid_attrs)\n      assert follow == Follows.get_follow!(follow.id)\n    end\n\n    test \"delete_follow/1 deletes the follow\" do\n      follow = follow_fixture()\n      assert {:ok, %Follow{}} = Follows.delete_follow(follow)\n      assert_raise Ecto.NoResultsError, fn -> Follows.get_follow!(follow.id) end\n    end\n\n    test \"change_follow/1 returns a follow changeset\" do\n      follow = follow_fixture()\n      assert %Ecto.Changeset{} = Follows.change_follow(follow)\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/follows/queries_test.exs",
    "content": "defmodule Mipha.Follows.QueriesTest do\n  use Mipha.DataCase\nend\n"
  },
  {
    "path": "test/mipha/notifications/notification_test.exs",
    "content": "defmodule Mipha.Notifications.NotificationTest do\n  use Mipha.DataCase\nend\n"
  },
  {
    "path": "test/mipha/notifications/notifications_test.exs",
    "content": "defmodule Mipha.NotificationsTest do\n  use Mipha.DataCase\nend\n"
  },
  {
    "path": "test/mipha/notifications/queries_test.exs",
    "content": "defmodule Mipha.Notifications.QueriesTest do\n  use Mipha.DataCase\nend\n"
  },
  {
    "path": "test/mipha/notifications/user_notification_test.exs",
    "content": "defmodule Mipha.Notifications.UserNotificationTest do\n  use Mipha.DataCase\nend\n"
  },
  {
    "path": "test/mipha/replies/queries_test.exs",
    "content": "defmodule Mipha.Replies.QueriesTest do\n  use Mipha.DataCase\nend\n"
  },
  {
    "path": "test/mipha/replies/replies_test.exs",
    "content": "defmodule Mipha.RepliesTest do\n  use Mipha.DataCase\n\n  alias Mipha.Replies\n\n  describe \"repies\" do\n    alias Mipha.Replies.Reply\n\n    @valid_attrs %{content: \"some content\", parent_id: 42, topic_id: 42, user_id: 42}\n    @update_attrs %{content: \"some updated content\", parent_id: 43, topic_id: 43, user_id: 43}\n    @invalid_attrs %{content: nil, parent_id: nil, topic_id: nil, user_id: nil}\n\n    def reply_fixture(attrs \\\\ %{}) do\n      {:ok, reply} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Replies.create_reply()\n\n      reply\n    end\n\n    test \"list_repies/0 returns all repies\" do\n      reply = reply_fixture()\n      assert Replies.list_repies() == [reply]\n    end\n\n    test \"get_reply!/1 returns the reply with given id\" do\n      reply = reply_fixture()\n      assert Replies.get_reply!(reply.id) == reply\n    end\n\n    test \"create_reply/1 with valid data creates a reply\" do\n      assert {:ok, %Reply{} = reply} = Replies.create_reply(@valid_attrs)\n      assert reply.content == \"some content\"\n      assert reply.parent_id == 42\n      assert reply.topic_id == 42\n      assert reply.user_id == 42\n    end\n\n    test \"create_reply/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Replies.create_reply(@invalid_attrs)\n    end\n\n    test \"update_reply/2 with valid data updates the reply\" do\n      reply = reply_fixture()\n      assert {:ok, reply} = Replies.update_reply(reply, @update_attrs)\n      assert %Reply{} = reply\n      assert reply.content == \"some updated content\"\n      assert reply.parent_id == 43\n      assert reply.topic_id == 43\n      assert reply.user_id == 43\n    end\n\n    test \"update_reply/2 with invalid data returns error changeset\" do\n      reply = reply_fixture()\n      assert {:error, %Ecto.Changeset{}} = Replies.update_reply(reply, @invalid_attrs)\n      assert reply == Replies.get_reply!(reply.id)\n    end\n\n    test \"change_reply/1 returns a reply changeset\" do\n      reply = reply_fixture()\n      assert %Ecto.Changeset{} = Replies.change_reply(reply)\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/replies/reply_test.exs",
    "content": "defmodule Mipha.Replies.ReplyTest do\n  use Mipha.DataCase\n\n  describe \"changeset\" do\n    test \"with valid attrs\" do\n    end\n\n    test \"with invalid attrs\" do\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/stars/star_test.exs",
    "content": "defmodule Mipha.Stars.StarTest do\n  use Mipha.DataCase\n\n  describe \"changeset\" do\n    test \"with valid attrs\" do\n    end\n\n    test \"with invalid attrs\" do\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/stars/stars_test.exs",
    "content": "defmodule Mipha.StarsTest do\n  use Mipha.DataCase\n\n  alias Mipha.Stars\n\n  describe \"stars\" do\n    alias Mipha.Stars.Star\n\n    @valid_attrs %{reply_id: 42, topic_id: 42, user_id: 42}\n    @update_attrs %{reply_id: 43, topic_id: 43, user_id: 43}\n    @invalid_attrs %{reply_id: nil, topic_id: nil, user_id: nil}\n\n    def star_fixture(attrs \\\\ %{}) do\n      {:ok, star} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Stars.create_star()\n\n      star\n    end\n\n    test \"list_stars/0 returns all stars\" do\n      star = star_fixture()\n      assert Stars.list_stars() == [star]\n    end\n\n    test \"get_star!/1 returns the star with given id\" do\n      star = star_fixture()\n      assert Stars.get_star!(star.id) == star\n    end\n\n    test \"create_star/1 with valid data creates a star\" do\n      assert {:ok, %Star{} = star} = Stars.create_star(@valid_attrs)\n      assert star.reply_id == 42\n      assert star.topic_id == 42\n      assert star.user_id == 42\n    end\n\n    test \"create_star/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Stars.create_star(@invalid_attrs)\n    end\n\n    test \"update_star/2 with valid data updates the star\" do\n      star = star_fixture()\n      assert {:ok, star} = Stars.update_star(star, @update_attrs)\n      assert %Star{} = star\n      assert star.reply_id == 43\n      assert star.topic_id == 43\n      assert star.user_id == 43\n    end\n\n    test \"update_star/2 with invalid data returns error changeset\" do\n      star = star_fixture()\n      assert {:error, %Ecto.Changeset{}} = Stars.update_star(star, @invalid_attrs)\n      assert star == Stars.get_star!(star.id)\n    end\n\n    test \"delete_star/1 deletes the star\" do\n      star = star_fixture()\n      assert {:ok, %Star{}} = Stars.delete_star(star)\n      assert_raise Ecto.NoResultsError, fn -> Stars.get_star!(star.id) end\n    end\n\n    test \"change_star/1 returns a star changeset\" do\n      star = star_fixture()\n      assert %Ecto.Changeset{} = Stars.change_star(star)\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/topics/node_test.exs",
    "content": "defmodule Mipha.Topics.NodeTest do\n  use Mipha.DataCase\n\n  alias Mipha.Topics.Node\n\n  @valid_attrs %{name: \"elixir mipha\"}\n  @invalid_attrs %{name: nil}\n\n  describe \"changeset\" do\n    test \"with valid attrs\" do\n      changeset = Node.changeset(%Node{}, @valid_attrs)\n      assert changeset.valid?\n    end\n\n    test \"with invalid attrs\" do\n      changeset = Node.changeset(%Node{}, @invalid_attrs)\n      refute changeset.valid?\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/topics/queries_test.exs",
    "content": "defmodule Mipha.Topics.QueriesTest do\n  use Mipha.DataCase\nend\n"
  },
  {
    "path": "test/mipha/topics/topic_test.exs",
    "content": "defmodule Mipha.Topics.TopicTest do\n  use Mipha.DataCase\n\n  alias Mipha.Topics.Topic\n\n  @valid_attrs %{title: \"elixir mipha\", body: \"elixir mipha body\", node_id: 1, user_id: 1}\n  @invalid_attrs %{title: nil, body: \"elixir mipha body\", node_id: nil}\n\n  describe \"changeset\" do\n    test \"with valid attrs\" do\n      changeset = Topic.changeset(%Topic{}, @valid_attrs)\n      assert changeset.valid?\n    end\n\n    test \"with invalid attrs\" do\n      changeset = Topic.changeset(%Topic{}, @invalid_attrs)\n      refute changeset.valid?\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha/topics/topics_test.exs",
    "content": "defmodule Mipha.TopicsTest do\n  use Mipha.DataCase\n\n  alias Mipha.{Topics, Repo}\n\n  describe \"topics\" do\n    alias Mipha.Topics.Topic\n\n    @valid_attrs %{\n      body: \"some body\",\n      closed_at: ~N[2010-04-17 14:00:00.000000],\n      last_reply_id: 42,\n      last_reply_user_id: 42,\n      node_id: 42,\n      replied_at: ~N[2010-04-17 14:00:00.000000],\n      reply_count: 42,\n      suggested_at: ~N[2010-04-17 14:00:00.000000],\n      title: \"some title\",\n      user_id: 42,\n      visit_count: 42\n    }\n    @update_attrs %{\n      body: \"some updated body\",\n      closed_at: ~N[2011-05-18 15:01:01.000000],\n      last_reply_id: 43,\n      last_reply_user_id: 43,\n      node_id: 43,\n      replied_at: ~N[2011-05-18 15:01:01.000000],\n      reply_count: 43,\n      suggested_at: ~N[2011-05-18 15:01:01.000000],\n      title: \"some updated title\",\n      user_id: 43,\n      visit_count: 43\n    }\n    @invalid_attrs %{\n      body: nil,\n      closed_at: nil,\n      last_reply_id: nil,\n      last_reply_user_id: nil,\n      node_id: nil,\n      replied_at: nil,\n      reply_count: nil,\n      suggested_at: nil,\n      title: nil,\n      user_id: nil,\n      visit_count: nil\n    }\n\n    def topic_fixture(attrs \\\\ %{}) do\n      {:ok, topic} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Topics.create_topic()\n\n      topic\n    end\n\n    test \"list_topics/0 returns all topics\" do\n      topic = topic_fixture()\n      assert Topics.list_topics() == [topic]\n    end\n\n    test \"get_topic!/1 returns the topic with given id\" do\n      topic = topic_fixture()\n\n      assert Topics.get_topic!(topic.id) ==\n               topic\n               |> Repo.preload([\n                 :node,\n                 :user,\n                 :last_reply_user,\n                 [replies: [:user, [parent: :user]]]\n               ])\n    end\n\n    test \"create_topic/1 with valid data creates a topic\" do\n      assert {:ok, %Topic{} = topic} = Topics.create_topic(@valid_attrs)\n      assert topic.body == \"some body\"\n      assert topic.closed_at == ~N[2010-04-17 14:00:00]\n      assert topic.last_reply_id == 42\n      assert topic.last_reply_user_id == 42\n      assert topic.node_id == 42\n      assert topic.replied_at == ~N[2010-04-17 14:00:00]\n      assert topic.reply_count == 42\n      assert topic.suggested_at == ~N[2010-04-17 14:00:00]\n      assert topic.title == \"some title\"\n      assert topic.user_id == 42\n      assert topic.visit_count == 42\n    end\n\n    test \"create_topic/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Topics.create_topic(@invalid_attrs)\n    end\n\n    test \"update_topic/2 with valid data updates the topic\" do\n      topic = topic_fixture()\n      assert {:ok, topic} = Topics.update_topic(topic, @update_attrs)\n      assert %Topic{} = topic\n      assert topic.body == \"some updated body\"\n      assert topic.closed_at == ~N[2011-05-18 15:01:01]\n      assert topic.last_reply_id == 43\n      assert topic.last_reply_user_id == 43\n      assert topic.node_id == 43\n      assert topic.replied_at == ~N[2011-05-18 15:01:01]\n      assert topic.reply_count == 43\n      assert topic.suggested_at == ~N[2011-05-18 15:01:01]\n      assert topic.title == \"some updated title\"\n      assert topic.user_id == 43\n      assert topic.visit_count == 43\n    end\n\n    test \"update_topic/2 with invalid data returns error changeset\" do\n      topic = topic_fixture()\n      assert {:error, %Ecto.Changeset{}} = Topics.update_topic(topic, @invalid_attrs)\n\n      assert topic\n             |> Repo.preload([:node, :user, :last_reply_user, [replies: [:user, [parent: :user]]]]) ==\n               Topics.get_topic!(topic.id)\n    end\n\n    test \"delete_topic/1 deletes the topic\" do\n      topic = topic_fixture()\n      assert {:ok, %Topic{}} = Topics.delete_topic(topic)\n      assert_raise Ecto.NoResultsError, fn -> Topics.get_topic!(topic.id) end\n    end\n\n    test \"change_topic/1 returns a topic changeset\" do\n      topic = topic_fixture()\n      assert %Ecto.Changeset{} = Topics.change_topic(topic)\n    end\n  end\n\n  describe \"nodes\" do\n    alias Mipha.Topics.Node\n\n    @valid_attrs %{name: \"some name\", parent_id: 42, position: 42, summary: \"some summary\"}\n    @update_attrs %{\n      name: \"some updated name\",\n      parent_id: 43,\n      position: 43,\n      summary: \"some updated summary\"\n    }\n    @invalid_attrs %{name: nil, parent_id: nil, position: nil, summary: nil}\n\n    def node_fixture(attrs \\\\ %{}) do\n      {:ok, node} =\n        attrs\n        |> Enum.into(@valid_attrs)\n        |> Topics.create_node()\n\n      node\n    end\n\n    test \"get_node!/1 returns the node with given id\" do\n      node = node_fixture()\n      assert Topics.get_node!(node.id) == node\n    end\n\n    test \"create_node/1 with valid data creates a node\" do\n      assert {:ok, %Node{} = node} = Topics.create_node(@valid_attrs)\n      assert node.name == \"some name\"\n      assert node.parent_id == 42\n      assert node.position == 42\n      assert node.summary == \"some summary\"\n    end\n\n    test \"create_node/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = Topics.create_node(@invalid_attrs)\n    end\n\n    test \"update_node/2 with valid data updates the node\" do\n      node = node_fixture()\n      assert {:ok, node} = Topics.update_node(node, @update_attrs)\n      assert %Node{} = node\n      assert node.name == \"some updated name\"\n      assert node.parent_id == 43\n      assert node.position == 43\n      assert node.summary == \"some updated summary\"\n    end\n\n    test \"update_node/2 with invalid data returns error changeset\" do\n      node = node_fixture()\n      assert {:error, %Ecto.Changeset{}} = Topics.update_node(node, @invalid_attrs)\n      assert node == Topics.get_node!(node.id)\n    end\n\n    test \"delete_node/1 deletes the node\" do\n      node = node_fixture()\n      assert {:ok, %Node{}} = Topics.delete_node(node)\n      assert_raise Ecto.NoResultsError, fn -> Topics.get_node!(node.id) end\n    end\n\n    test \"change_node/1 returns a node changeset\" do\n      node = node_fixture()\n      assert %Ecto.Changeset{} = Topics.change_node(node)\n    end\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/channels/room_channel_test.exs",
    "content": "defmodule MiphaWeb.RoomChannelTest do\n  use MiphaWeb.ChannelCase\n\n  # alias MiphaWeb.RoomChannel\n\n  # setup do\n  #   {:ok, _, socket} =\n  #     socket(\"user_id\", %{some: :assign})\n  #     |> subscribe_and_join(RoomChannel, \"room:lobby\")\n\n  #   {:ok, socket: socket}\n  # end\n\n  # test \"ping replies with status ok\", %{socket: socket} do\n  #   ref = push socket, \"ping\", %{\"hello\" => \"there\"}\n  #   assert_reply ref, :ok, %{\"hello\" => \"there\"}\n  # end\n\n  # test \"shout broadcasts to room:lobby\", %{socket: socket} do\n  #   push socket, \"shout\", %{\"hello\" => \"all\"}\n  #   assert_broadcast \"shout\", %{\"hello\" => \"all\"}\n  # end\n\n  # test \"broadcasts are pushed to the client\", %{socket: socket} do\n  #   broadcast_from! socket, \"broadcast\", %{\"some\" => \"data\"}\n  #   assert_push \"broadcast\", %{\"some\" => \"data\"}\n  # end\nend\n"
  },
  {
    "path": "test/mipha_web/channels/topic_channel_test.exs",
    "content": "defmodule MiphaWeb.TopicChannelTest do\n  use MiphaWeb.ChannelCase\n\n  # alias MiphaWeb.TopicChannel\n\n  # setup do\n  #   {:ok, _, socket} =\n  #     socket(\"user_id\", %{some: :assign})\n  #     |> subscribe_and_join(TopicChannel, \"topic:lobby\")\n\n  #   {:ok, socket: socket}\n  # end\n\n  # test \"ping replies with status ok\", %{socket: socket} do\n  #   ref = push socket, \"ping\", %{\"hello\" => \"there\"}\n  #   assert_reply ref, :ok, %{\"hello\" => \"there\"}\n  # end\n\n  # test \"shout broadcasts to topic:lobby\", %{socket: socket} do\n  #   push socket, \"shout\", %{\"hello\" => \"all\"}\n  #   assert_broadcast \"shout\", %{\"hello\" => \"all\"}\n  # end\n\n  # test \"broadcasts are pushed to the client\", %{socket: socket} do\n  #   broadcast_from! socket, \"broadcast\", %{\"some\" => \"data\"}\n  #   assert_push \"broadcast\", %{\"some\" => \"data\"}\n  # end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/admin/company_controller_test.exs",
    "content": "defmodule MiphaWeb.Admin.CompanyControllerTest do\n  use MiphaWeb.ConnCase\n\n  alias Mipha.Accounts.Company\n\n  @tag :as_admin\n  test \"lists all companies on index\", %{conn: conn} do\n    c1 = insert(:company)\n    c2 = insert(:company)\n\n    conn = get(conn, admin_company_path(conn, :index))\n    assert conn.status == 200\n    assert conn.resp_body =~ c1.name\n    assert conn.resp_body =~ c2.name\n  end\n\n  @tag :as_admin\n  test \"deletes company with redirects\", %{conn: conn} do\n    c = insert(:company)\n    conn = delete(conn, admin_company_path(conn, :delete, c.id))\n\n    assert redirected_to(conn) == admin_company_path(conn, :index)\n    assert count(Company) == 0\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/admin/node_controller_test.exs",
    "content": "defmodule MiphaWeb.Admin.NodeControllerTest do\n  use MiphaWeb.ConnCase\n\n  alias Mipha.Topics.Node\n\n  @valid_attrs %{name: \"some node\"}\n  @invalid_attrs %{name: nil}\n\n  @tag :as_admin\n  test \"lists all nodes on index\", %{conn: conn} do\n    # n1 = insert(:node)\n    # n2 = insert(:node)\n    conn = get(conn, admin_node_path(conn, :index))\n    assert conn.status == 200\n    # assert conn.resp_body =~ n1.name\n    # assert conn.resp_body =~ n2.name\n  end\n\n  @tag :as_admin\n  test \"creates node with valid attributes, and redirects\", %{conn: conn} do\n    conn = post(conn, admin_node_path(conn, :create), node: @valid_attrs)\n    node = Repo.one(from t in Node, where: t.name == ^@valid_attrs[:name])\n\n    assert redirected_to(conn) == admin_node_path(conn, :show, node.id)\n    assert node.name == @valid_attrs[:name]\n  end\n\n  @tag :as_admin\n  test \"does not create node with invalid attributes\", %{conn: conn} do\n    count_before = count(Node)\n    conn = post(conn, admin_node_path(conn, :create), node: @invalid_attrs)\n\n    assert conn.status == 200\n    assert count(Node) == count_before\n  end\n\n  @tag :as_admin\n  test \"updates node\", %{conn: conn} do\n    n = insert(:node)\n    conn = put(conn, admin_node_path(conn, :update, n.id), node: @valid_attrs)\n\n    assert redirected_to(conn) == admin_node_path(conn, :show, n.id)\n  end\n\n  # @tag :as_admin\n  # test \"renders form to create new node\", %{conn: conn} do\n  #   conn = get(conn, admin_node_path(conn, :new))\n  #   assert html_response(conn, 200) =~ ~r/new/\n  # end\n\n  # @tag :as_admin\n  # test \"renders form to create edit node\", %{conn: conn} do\n  #   n = insert(:node)\n  #   conn = get(conn, admin_node_path(conn, :edit, n.id))\n  #   assert html_response(conn, 200) =~ ~r/edit/\n  # end\n\n  @tag :as_admin\n  test \"deletes node\", %{conn: conn} do\n    n = insert(:node)\n    conn = delete(conn, admin_node_path(conn, :delete, n.id))\n\n    assert redirected_to(conn) == admin_node_path(conn, :index)\n    # assert count(Node) == 0\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/admin/page_controller_test.exs",
    "content": "defmodule MiphaWeb.Admin.PageControllerTest do\n  use MiphaWeb.ConnCase\n\n  @tag :as_admin\n  test \"GET /\", %{conn: conn} do\n    conn = get(conn, admin_page_path(conn, :index))\n    assert conn.status == 200\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/admin/reply_controller_test.exs",
    "content": "defmodule MiphaWeb.Admin.ReplyControllerTest do\n  use MiphaWeb.ConnCase\n\n  @tag :as_admin\n  test \"lists all replies on index\", %{conn: conn} do\n    r1 = insert(:reply)\n    r2 = insert(:reply)\n    conn = get(conn, admin_reply_path(conn, :index))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ r1.content\n    assert conn.resp_body =~ r2.content\n  end\n\n  @tag :as_admin\n  test \"deletes reply, and redirects\", %{conn: conn} do\n    r = insert(:reply)\n    conn = delete(conn, admin_reply_path(conn, :delete, r.id))\n\n    assert redirected_to(conn) == admin_reply_path(conn, :index)\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/admin/team_controller_test.exs",
    "content": "defmodule MiphaWeb.Admin.TeamControllerTest do\n  use MiphaWeb.ConnCase\n\n  @tag :as_admin\n  test \"lists all teams on index\", %{conn: conn} do\n    t1 = insert(:team)\n    t2 = insert(:team)\n    conn = get(conn, admin_team_path(conn, :index))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.name\n    assert conn.resp_body =~ t2.name\n  end\n\n  @tag :as_admin\n  test \"deletes team, and redirectes\", %{conn: conn} do\n    t = insert(:team)\n    conn = delete(conn, admin_team_path(conn, :delete, t.id))\n\n    assert redirected_to(conn) == admin_team_path(conn, :index)\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/admin/topic_controller_test.exs",
    "content": "defmodule MiphaWeb.Admin.TopicControllerTest do\n  use MiphaWeb.ConnCase\n\n  @tag :as_admin\n  test \"lists all topics on index\", %{conn: conn} do\n    t1 = insert(:topic)\n    t2 = insert(:topic)\n\n    conn = get(conn, admin_topic_path(conn, :index))\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.title\n    assert conn.resp_body =~ t2.title\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/admin/user_controller_test.exs",
    "content": "defmodule MiphaWeb.Admin.UserControllerTest do\n  use MiphaWeb.ConnCase\n\n  @tag :as_admin\n  test \"lists all users on index\", %{conn: conn} do\n    u1 = insert(:user)\n    u2 = insert(:user)\n\n    conn = get(conn, admin_user_path(conn, :index))\n    assert conn.status == 200\n    assert conn.resp_body =~ u1.username\n    assert conn.resp_body =~ u2.username\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/auth_controller_test.exs",
    "content": "defmodule MiphaWeb.AuthControllerTest do\n  use MiphaWeb.ConnCase\n\n  test \"getting the sign in form\" do\n    # conn = get(conn, auth_path(conn, :login))\n    # assert html_response(conn, 200) =~ \"Sign In\"\n  end\n\n  test \"submitting the form with unknow username/email\" do\n  end\n\n  test \"submitting the form with wrong password\" do\n  end\n\n  describe \"github auth\" do\n    test \"successful auth on existing person signs you in\" do\n    end\n\n    test \"failed auth doesn't sign you in\" do\n    end\n  end\n\n  test \"successful sign with correct infomation\" do\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/location_controller_test.exs",
    "content": "defmodule MiphaWeb.LocationControllerTest do\n  use MiphaWeb.ConnCase\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/notification_controller_test.exs",
    "content": "defmodule MiphaWeb.NotificationControllerTest do\n  use MiphaWeb.ConnCase\n\n  @tag :as_user\n  test \"lists all notifications\", %{conn: conn} do\n    # n1 = insert(:notification, user: user)\n    # n2 = insert(:notification, user: user)\n    conn = get(conn, notification_path(conn, :index))\n\n    assert conn.status == 200\n  end\n\n  @tag :as_user\n  test \"makes user notifications read\" do\n  end\n\n  @tag :as_user\n  test \"deletes user notification\" do\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/page_controller_test.exs",
    "content": "defmodule MiphaWeb.PageControllerTest do\n  use MiphaWeb.ConnCase\n\n  test \"GET /\", %{conn: conn} do\n    t1 = insert(:topic)\n    t2 = insert(:topic)\n\n    conn = get(conn, topic_path(conn, :index))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.title\n    assert conn.resp_body =~ t2.title\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/reply_controller_test.exs",
    "content": "defmodule MiphaWeb.ReplyControllerTest do\n  use MiphaWeb.ConnCase\n\n  test \"renders reply edit form page\" do\n  end\n\n  test \"deletes reply, and redirectes\" do\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/session_controller_test.exs",
    "content": "defmodule MiphaWeb.SessionControllerTest do\n  use MiphaWeb.ConnCase\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/setting_controller_test.exs",
    "content": "defmodule MiphaWeb.SettingControllerTest do\n  use MiphaWeb.ConnCase\n\n  @tag :as_user\n  test \"renders profile page\", %{conn: conn, user: user} do\n    conn = get(conn, setting_path(conn, :show))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ user.username\n  end\n\n  @tag :as_user\n  test \"renders update password page\", %{conn: conn} do\n    conn = get(conn, setting_password_path(conn, :password))\n    assert conn.status == 200\n  end\n\n  @tag :as_user\n  test \"renders reward page\", %{conn: conn} do\n    conn = get(conn, setting_reward_path(conn, :reward))\n    assert conn.status == 200\n  end\n\n  @tag :as_user\n  test \"renders account page\", %{conn: conn} do\n    conn = get(conn, setting_account_path(conn, :account))\n    assert conn.status == 200\n  end\n\n  @tag :as_user\n  test \"updates profile\" do\n  end\n\n  @tag :as_user\n  test \"updates account\" do\n  end\n\n  @tag :as_user\n  test \"updates reward\" do\n  end\n\n  @tag :as_user\n  test \"updates user password\" do\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/topic_controller_test.exs",
    "content": "defmodule MiphaWeb.TopicControllerTest do\n  use MiphaWeb.ConnCase\n\n  alias Mipha.Topics.Topic\n\n  import Mipha.Stars, only: [has_starred?: 1]\n  import Mipha.Collections, only: [has_collected?: 1]\n\n  @valid_attrs %{title: \"elixir mipha\", body: \"elixir mipha body\", node_id: 1}\n  @invalid_attrs %{title: nil, body: \"elixir mipha body\", node_id: nil}\n\n  test \"gets the topics index\", %{conn: conn} do\n    t1 = insert(:topic)\n    t2 = insert(:topic)\n    conn = get(conn, topic_path(conn, :index))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.title\n    assert conn.resp_body =~ t2.title\n  end\n\n  test \"gets the no_reply topics index\", %{conn: conn} do\n    t1 = insert(:topic)\n    conn = get(conn, topic_path(conn, :no_reply))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.title\n  end\n\n  test \"gets the popular topics index\", %{conn: conn} do\n    t1 = insert(:topic, reply_count: 15, last_reply_user: build(:user))\n    conn = get(conn, topic_path(conn, :popular))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.title\n  end\n\n  test \"gets the featured topics index\", %{conn: conn} do\n    t1 = insert(:topic, type: :featured)\n    conn = get(conn, topic_path(conn, :featured))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.title\n  end\n\n  test \"gets the job topics index\", %{conn: conn} do\n    t1 = insert(:topic, type: :job)\n    conn = get(conn, topic_path(conn, :jobs))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.title\n  end\n\n  test \"gets the single node topics index\", %{conn: conn} do\n    t1 = insert(:topic)\n    conn = get(conn, topic_path(conn, :index, node_id: t1.node_id))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.title\n  end\n\n  test \"gets the educational topics index\", %{conn: conn} do\n    t1 = insert(:topic, type: :educational)\n    conn = get(conn, topic_path(conn, :educational))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ t1.title\n  end\n\n  @tag :as_user\n  test \"stars topic\", %{conn: conn, user: user} do\n    t = insert(:topic, user: user)\n    conn = post(conn, topic_path(conn, :star, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == t.id, preload: [:user])\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    assert has_starred?(user: topic.user, topic: topic)\n  end\n\n  @tag :as_user\n  test \"unstars topic\", %{conn: conn, user: user} do\n    t = insert(:topic, user: user)\n    _star = insert(:star, topic: t, user: user)\n    conn = post(conn, topic_path(conn, :unstar, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == t.id, preload: [:user])\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    refute has_starred?(user: topic.user, topic: topic)\n  end\n\n  @tag :as_user\n  test \"collections topic\", %{conn: conn, user: user} do\n    t = insert(:topic, user: user)\n    conn = post(conn, topic_path(conn, :collection, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == t.id, preload: [:user])\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    assert has_collected?(user: topic.user, topic: topic)\n  end\n\n  @tag :as_user\n  test \"uncollections topic\", %{conn: conn, user: user} do\n    t = insert(:topic, user: user)\n    _collection = insert(:collection, topic: t, user: user)\n    conn = post(conn, topic_path(conn, :uncollection, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == t.id, preload: [:user])\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    refute has_collected?(user: topic.user, topic: topic)\n  end\n\n  @tag :as_admin\n  test \"suggests topic\", %{conn: conn} do\n    t = insert(:topic, suggested_at: nil)\n    conn = post(conn, topic_path(conn, :suggest, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == ^t.id)\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    assert topic.suggested_at != nil\n  end\n\n  @tag :as_admin\n  test \"unsuggests topic\", %{conn: conn} do\n    t = insert(:topic, suggested_at: Timex.now())\n    conn = post(conn, topic_path(conn, :unsuggest, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == ^t.id)\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    assert topic.suggested_at == nil\n  end\n\n  @tag :as_user\n  test \"closes topic\", %{conn: conn} do\n    t = insert(:topic, closed_at: nil)\n    conn = post(conn, topic_path(conn, :close, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == ^t.id)\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    assert topic.closed_at != nil\n  end\n\n  @tag :as_user\n  test \"reopens topic\", %{conn: conn} do\n    t = insert(:topic, closed_at: Timex.now())\n    conn = post(conn, topic_path(conn, :open, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == ^t.id)\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    assert topic.closed_at == nil\n  end\n\n  @tag :as_admin\n  test \"makes featured topic with admin\", %{conn: conn} do\n    t = insert(:topic, type: :normal)\n    conn = post(conn, topic_path(conn, :excellent, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == ^t.id)\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    assert topic.type == :featured\n  end\n\n  @tag :as_admin\n  test \"makes normal topic with admin\", %{conn: conn} do\n    t = insert(:topic, type: :featured)\n    conn = post(conn, topic_path(conn, :normal, t.id))\n    topic = Repo.one(from t in Topic, where: t.id == ^t.id)\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    assert topic.type == :normal\n  end\n\n  @tag :as_user\n  test \"updates topic and redirects\", %{conn: conn} do\n    t = insert(:topic)\n    conn = put(conn, topic_path(conn, :update, t.id), topic: @valid_attrs)\n\n    assert redirected_to(conn) == topic_path(conn, :show, t.id)\n    assert count(Topic) == 1\n  end\n\n  @tag :as_user\n  test \"renders form to create new topic\", %{conn: conn} do\n    conn = get(conn, topic_path(conn, :new))\n    assert html_response(conn, 200) =~ ~r/new/\n  end\n\n  @tag :as_user\n  test \"renders form to create edit topic\", %{conn: conn} do\n    t = insert(:topic)\n    conn = get(conn, topic_path(conn, :edit, t.id))\n\n    assert html_response(conn, 200) =~ ~r/edit/\n  end\n\n  @tag :as_user\n  test \"creates topic with valid attributes, and redirectes\", %{conn: conn} do\n    conn = post(conn, topic_path(conn, :create), topic: @valid_attrs)\n    topic = Repo.one(from t in Topic, where: t.title == ^@valid_attrs[:title])\n\n    assert redirected_to(conn) == topic_path(conn, :show, topic.id)\n    assert topic.title == @valid_attrs[:title]\n  end\n\n  @tag :as_user\n  test \"does not create topic with invalid attributes\", %{conn: conn} do\n    count_before = count(Topic)\n    conn = post(conn, topic_path(conn, :create), topic: @invalid_attrs)\n\n    assert conn.status == 200\n    assert count(Topic) == count_before\n  end\n\n  test \"gets a topic show page\", %{conn: conn} do\n    t = insert(:topic)\n    conn = get(conn, topic_path(conn, :show, t.id))\n\n    assert html_response(conn, 200) =~ t.title\n  end\n\n  # test \"getting a topic page that doesn't exist\", %{conn: conn} do\n  #   assert_raise Ecto.NoResultsError, fn ->\n  #     get conn, topic_path(conn, :show, \"bad-id\")\n  #   end\n  # end\n\n  test \"requires user auth on any acitons\", %{conn: conn} do\n    t = insert(:topic)\n\n    Enum.each(\n      [\n        get(conn, topic_path(conn, :new)),\n        post(conn, topic_path(conn, :create), topic: @valid_attrs),\n        get(conn, topic_path(conn, :edit, t.id)),\n        put(conn, topic_path(conn, :update, t.id), topic: @valid_attrs),\n        delete(conn, topic_path(conn, :delete, t.id))\n      ],\n      fn conn ->\n        assert html_response(conn, 302)\n      end\n    )\n  end\n\n  @tag :as_user\n  test \"requires admin auth on any acitons\", %{conn: conn} do\n    t = insert(:topic)\n\n    Enum.each(\n      [\n        post(conn, topic_path(conn, :unsuggest, t.id)),\n        post(conn, topic_path(conn, :suggest, t.id)),\n        post(conn, topic_path(conn, :normal, t.id)),\n        post(conn, topic_path(conn, :excellent, t.id))\n      ],\n      fn conn ->\n        assert html_response(conn, 302)\n      end\n    )\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/controllers/user_controller_test.exs",
    "content": "defmodule MiphaWeb.UserControllerTest do\n  use MiphaWeb.ConnCase\n\n  import Mipha.Follows, only: [has_followed?: 1]\n\n  test \"lists active users\", %{conn: conn} do\n    u1 = insert(:user)\n    u2 = insert(:user)\n    conn = get(conn, user_path(conn, :index))\n\n    assert conn.status == 200\n    assert conn.resp_body =~ u1.username\n    assert conn.resp_body =~ u2.username\n  end\n\n  # test \"renders single user dashboard\", %{conn: conn} do\n  #   u1 = insert(:user)\n  #   conn = get(conn, user_path(conn, :show, u1.username))\n\n  #   assert conn.status == 200\n  #   assert conn.resp_body =~ u1.username\n  # end\n\n  # test \"renders user topic lists\", %{conn: conn} do\n  #   t = insert(:topic)\n  #   conn = get(conn, user_topics_path(conn, :topics, t.user.username))\n\n  #   assert conn.status == 200\n  #   assert conn.resp_body =~ t.title\n  # end\n\n  # test \"renders user reply lists\", %{conn: conn} do\n  #   r = insert(:reply)\n  #   conn = get(conn, user_replies_path(conn, :replies, r.user.username))\n\n  #   assert conn.status == 200\n  #   assert conn.resp_body =~ r.content\n  # end\n\n  # test \"renders user followers user lists\", %{conn: conn} do\n  #   f = insert(:follow)\n  #   conn = get(conn, user_followers_path(conn, :followers, f.user.username))\n\n  #   assert conn.status == 200\n  #   assert conn.resp_body =~ f.follower.username\n  # end\n\n  # test \"renders user following user lists\", %{conn: conn} do\n  #   f = insert(:follow)\n  #   conn = get(conn, user_following_path(conn, :following, f.follower.username))\n\n  #   assert conn.status == 200\n  #   assert conn.resp_body =~ f.user.username\n  # end\n\n  # test \"renders user collection lists\", %{conn: conn} do\n  #   c = insert(:collection)\n  #   conn = get(conn, user_collections_path(conn, :collections, c.user.username))\n\n  #   assert conn.status == 200\n  #   assert conn.resp_body =~ c.topic.title\n  # end\n\n  @tag :as_user\n  test \"follows user\", %{conn: conn, user: user} do\n    u = insert(:user)\n    conn = post(conn, user_follow_path(conn, :follow, u.username))\n\n    assert redirected_to(conn) == user_path(conn, :show, u.username)\n    assert has_followed?(user: u, follower: user) == true\n  end\n\n  @tag :as_user\n  test \"unfollows user\", %{conn: conn, user: user} do\n    u = insert(:user)\n    _follow = insert(:follow, follower: user, user: u)\n    conn = post(conn, user_unfollow_path(conn, :unfollow, u.username))\n\n    assert redirected_to(conn) == user_path(conn, :show, u.username)\n    assert has_followed?(user: u, follower: user) == false\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/views/error_view_test.exs",
    "content": "defmodule MiphaWeb.ErrorViewTest do\n  use MiphaWeb.ConnCase, async: true\n\n  # Bring render/3 and render_to_string/3 for testing custom views\n  import Phoenix.View\n\n  test \"renders 404.html\" do\n    assert render_to_string(MiphaWeb.ErrorView, \"404.html\", []) == \"Not Found\"\n  end\n\n  test \"renders 500.html\" do\n    assert render_to_string(MiphaWeb.ErrorView, \"500.html\", []) == \"Internal Server Error\"\n  end\nend\n"
  },
  {
    "path": "test/mipha_web/views/layout_view_test.exs",
    "content": "defmodule MiphaWeb.LayoutViewTest do\n  use MiphaWeb.ConnCase, async: true\nend\n"
  },
  {
    "path": "test/mipha_web/views/page_view_test.exs",
    "content": "defmodule MiphaWeb.PageViewTest do\n  use MiphaWeb.ConnCase, async: true\nend\n"
  },
  {
    "path": "test/support/channel_case.ex",
    "content": "defmodule MiphaWeb.ChannelCase do\n  @moduledoc \"\"\"\n  This module defines the test case to be used by\n  channel tests.\n\n  Such tests rely on `Phoenix.ChannelTest` and also\n  import other functionality to make it easier\n  to build common datastructures and query the data layer.\n\n  Finally, if the test case interacts with the database,\n  it cannot be async. For this reason, every test runs\n  inside a transaction which is reset at the beginning\n  of the test unless the test case is marked as async.\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      # Import conveniences for testing with channels\n      use Phoenix.ChannelTest\n\n      # The default endpoint for testing\n      @endpoint MiphaWeb.Endpoint\n    end\n  end\n\n  setup tags do\n    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Mipha.Repo)\n\n    unless tags[:async] do\n      Ecto.Adapters.SQL.Sandbox.mode(Mipha.Repo, {:shared, self()})\n    end\n\n    :ok\n  end\nend\n"
  },
  {
    "path": "test/support/conn_case.ex",
    "content": "defmodule MiphaWeb.ConnCase do\n  @moduledoc \"\"\"\n  This module defines the test case to be used by\n  tests that require setting up a connection.\n\n  Such tests rely on `Phoenix.ConnTest` and also\n  import other functionality to make it easier\n  to build common datastructures and query the data layer.\n\n  Finally, if the test case interacts with the database,\n  it cannot be async. For this reason, every test runs\n  inside a transaction which is reset at the beginning\n  of the test unless the test case is marked as async.\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      # Import conveniences for testing with connections\n      import Plug.Conn\n      import Phoenix.ConnTest\n\n      alias Mipha.Repo\n      import Ecto\n      import Ecto.Query, only: [from: 2]\n\n      defp count(query), do: Repo.aggregate(query, :count, :id)\n\n      import MiphaWeb.Router.Helpers\n      import Mipha.Factory\n      # The default endpoint for testing\n      @endpoint MiphaWeb.Endpoint\n    end\n  end\n\n  setup tags do\n    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Mipha.Repo)\n\n    unless tags[:async] do\n      Ecto.Adapters.SQL.Sandbox.mode(Mipha.Repo, {:shared, self()})\n    end\n\n    user =\n      cond do\n        tags[:as_admin] -> Mipha.Factory.insert(:user, is_admin: true)\n        tags[:as_user] -> Mipha.Factory.insert(:user, is_admin: false)\n        true -> nil\n      end\n\n    conn =\n      Phoenix.ConnTest.build_conn()\n      |> Plug.Conn.assign(:current_user, user)\n\n    {:ok, conn: conn, user: user}\n  end\nend\n"
  },
  {
    "path": "test/support/data_case.ex",
    "content": "defmodule Mipha.DataCase do\n  @moduledoc \"\"\"\n  This module defines the setup for tests requiring\n  access to the application's data layer.\n\n  You may define functions here to be used as helpers in\n  your tests.\n\n  Finally, if the test case interacts with the database,\n  it cannot be async. For this reason, every test runs\n  inside a transaction which is reset at the beginning\n  of the test unless the test case is marked as async.\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      alias Mipha.Repo\n\n      import Ecto\n      import Ecto.Changeset\n      import Ecto.Query\n      import Mipha.DataCase\n    end\n  end\n\n  setup tags do\n    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Mipha.Repo)\n\n    unless tags[:async] do\n      Ecto.Adapters.SQL.Sandbox.mode(Mipha.Repo, {:shared, self()})\n    end\n\n    :ok\n  end\n\n  @doc \"\"\"\n  A helper that transform changeset errors to a map of messages.\n\n      assert {:error, changeset} = Accounts.create_user(%{password: \"short\"})\n      assert \"password is too short\" in errors_on(changeset).password\n      assert %{password: [\"password is too short\"]} = errors_on(changeset)\n\n  \"\"\"\n  def errors_on(changeset) do\n    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->\n      Enum.reduce(opts, message, fn {key, value}, acc ->\n        String.replace(acc, \"%{#{key}}\", to_string(value))\n      end)\n    end)\n  end\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "{:ok, _} = Application.ensure_all_started(:ex_machina)\n\nExUnit.start()\nEcto.Adapters.SQL.Sandbox.mode(Mipha.Repo, :manual)\n"
  }
]