├── ClientApp ├── .browserslistrc ├── .prettierrc ├── src │ ├── assets │ │ └── background2.jpg │ ├── reset.css │ ├── index.jsx │ ├── template.html │ ├── api │ │ └── index.js │ ├── App.jsx │ └── components │ │ └── Ranking.jsx ├── .gitignore ├── webpack │ ├── paths.js │ ├── webpack.dev.js │ ├── webpack.prod.js │ └── webpack.common.js ├── webpack.config.js ├── .babelrc ├── LICENSE ├── .eslintrc └── package.json ├── Caddyfile ├── Pages ├── _ViewImports.cshtml ├── Error.cshtml.cs └── Error.cshtml ├── appsettings.json ├── appsettings.Development.json ├── .dockerignore ├── Dockerfile ├── Program.cs ├── .github └── workflows │ └── publish.yaml ├── ranking-showcase.csproj ├── Controllers └── AnimeController.cs ├── Startup.cs ├── Services ├── RankingMgr.cs └── FileSync.cs └── .gitignore /ClientApp/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 0.5% 2 | last 2 versions 3 | Firefox ESR 4 | not dead 5 | -------------------------------------------------------------------------------- /ClientApp/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /Caddyfile: -------------------------------------------------------------------------------- 1 | https://ranking.ikely.me { 2 | proxy / http://app:5000 { 3 | transparent 4 | websocket 5 | } 6 | } -------------------------------------------------------------------------------- /ClientApp/src/assets/background2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wattlebird/rankit-showcase/HEAD/ClientApp/src/assets/background2.jpg -------------------------------------------------------------------------------- /ClientApp/src/reset.css: -------------------------------------------------------------------------------- 1 | .ant-layout { 2 | background-color: #020111; 3 | } 4 | 5 | .ant-layout-header { 6 | background-color: transparent; 7 | } 8 | -------------------------------------------------------------------------------- /Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using ranking_showcase 2 | @namespace ranking_showcase.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | out/ 4 | .git/ 5 | .gitignore/ 6 | .vscode/ 7 | Pipeline/ 8 | caddy.log 9 | customrank_*.csv 10 | caddy 11 | Caddyfile 12 | 13 | #JS 14 | ClientApp/build/ 15 | ClientApp/node_modules/ 16 | ClientApp/*.md -------------------------------------------------------------------------------- /ClientApp/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 4 | import { LocaleProvider } from 'antd'; 5 | import App from './App.jsx'; 6 | 7 | import 'reset.css'; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('app'), 14 | ); 15 | -------------------------------------------------------------------------------- /ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | # Cruft 2 | *~ 3 | *# 4 | .#* 5 | .DS_Store 6 | 7 | # Test 8 | coverage 9 | .eslintcache 10 | 11 | # Logs 12 | *.log 13 | 14 | # npm & yarn 15 | node_modules/ 16 | .npm 17 | .yarn-integrity 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | yarn.lock* 22 | package-lock.json 23 | /build/bundle.js 24 | /build/index.html 25 | /build* 26 | /build/* 27 | # Editor/IDE 28 | .idea 29 | packages/api/etc/ 30 | -------------------------------------------------------------------------------- /ClientApp/webpack/paths.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | root: path.resolve(__dirname, '../'), 5 | outputPath: path.resolve(__dirname, '../', 'build'), 6 | entryPath: path.resolve(__dirname, '../', 'src/index.jsx'), 7 | templatePath: path.resolve(__dirname, '../', 'src/template.html'), 8 | imagesFolder: 'images', 9 | fontsFolder: 'fonts', 10 | cssFolder: 'css', 11 | jsFolder: 'js', 12 | }; 13 | -------------------------------------------------------------------------------- /ClientApp/src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bangumi Research - Scientific animation ranking 7 | 8 | 9 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ClientApp/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpackMerge = require('webpack-merge'); 2 | const common = require('./webpack/webpack.common'); 3 | 4 | const envs = { 5 | development: 'dev', 6 | production: 'prod', 7 | }; 8 | /* eslint-disable global-require,import/no-dynamic-require */ 9 | const env = envs[process.env.NODE_ENV || 'development']; 10 | const envConfig = require(`./webpack/webpack.${env}.js`); 11 | module.exports = webpackMerge(common, envConfig); 12 | -------------------------------------------------------------------------------- /ClientApp/src/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const baseUrl = '/api/anime'; 4 | 5 | function ReadAll(start = 0, len = 50000) { 6 | return axios 7 | .get(`${baseUrl}/list`, { 8 | params: { 9 | start, 10 | length: len, 11 | }, 12 | }) 13 | .then(data => data.data); 14 | } 15 | 16 | function Search(q) { 17 | return axios.get(`${baseUrl}/search`, { 18 | params: { 19 | q, 20 | }, 21 | }); 22 | } 23 | 24 | function Date() { 25 | return axios.get(`${baseUrl}/date`).then(data => data.data); 26 | } 27 | 28 | export default { 29 | ReadAll, 30 | Search, 31 | Date, 32 | }; 33 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-sdk AS build 2 | WORKDIR /app 3 | 4 | # copy csproj and restore as distinct layers 5 | COPY *.cs *.csproj *.json ./ 6 | COPY ClientApp ./ClientApp 7 | COPY Controllers ./Controllers 8 | COPY Pages ./Pages 9 | COPY Services ./Services 10 | RUN dotnet restore 11 | 12 | RUN curl -sL https://deb.nodesource.com/setup_10.x | bash && \ 13 | apt-get install -y nodejs 14 | 15 | # copy everything else and build app 16 | RUN dotnet publish -c Release -o out 17 | 18 | 19 | FROM microsoft/dotnet:2.1-aspnetcore-runtime AS runtime 20 | WORKDIR /app 21 | COPY --from=build /app/out ./ 22 | EXPOSE 5000 23 | ENTRYPOINT ["dotnet", "ranking-showcase.dll"] 24 | -------------------------------------------------------------------------------- /Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | 9 | namespace ranking_showcase.Pages 10 | { 11 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 12 | public class ErrorModel : PageModel 13 | { 14 | public string RequestId { get; set; } 15 | 16 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 17 | 18 | public void OnGet() 19 | { 20 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace ranking_showcase 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup().UseUrls("http://0.0.0.0:5000"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 |

21 |

22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |

24 | -------------------------------------------------------------------------------- /ClientApp/webpack/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | const commonPaths = require('./paths'); 4 | 5 | module.exports = { 6 | mode: 'development', 7 | output: { 8 | filename: '[name].js', 9 | path: commonPaths.outputPath, 10 | chunkFilename: '[name].js', 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.css$/, 16 | use: [ 17 | 'style-loader', 18 | { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: true, 22 | // modules: true, 23 | // camelCase: true, 24 | // localIdentName: '[local]___[hash:base64:5]', 25 | }, 26 | }, 27 | ], 28 | }, 29 | ], 30 | }, 31 | devServer: { 32 | contentBase: commonPaths.outputPath, 33 | compress: true, 34 | hot: true, 35 | port: 3000, 36 | }, 37 | plugins: [new webpack.HotModuleReplacementPlugin()], 38 | }; 39 | -------------------------------------------------------------------------------- /ClientApp/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": [ 4 | "@babel/plugin-transform-runtime", 5 | "react-hot-loader/babel", 6 | 7 | // Stage 2 https://github.com/babel/babel/tree/master/packages/babel-preset-stage-2 8 | ["@babel/plugin-proposal-decorators", { 9 | "legacy": true 10 | }], 11 | "@babel/plugin-proposal-function-sent", 12 | "@babel/plugin-proposal-export-namespace-from", 13 | "@babel/plugin-proposal-numeric-separator", 14 | "@babel/plugin-proposal-throw-expressions", 15 | 16 | // Stage 3 17 | "@babel/plugin-syntax-dynamic-import", 18 | "@babel/plugin-syntax-import-meta", 19 | ["@babel/plugin-proposal-class-properties", { 20 | "loose": true 21 | }], 22 | "@babel/plugin-proposal-json-strings", 23 | ["import", { 24 | "libraryName": "antd", 25 | "libraryDirectory": "es", 26 | "style": "css" // `style: true` 会加载 less 文件 27 | }] 28 | ] 29 | 30 | } 31 | -------------------------------------------------------------------------------- /ClientApp/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hashem Khalifa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI/CD 2 | 3 | on: 4 | push: 5 | # Publish `master` as Docker `latest` image. 6 | branches: 7 | - master 8 | 9 | # Publish `v1.2.3` tags as releases. 10 | tags: 11 | - v* 12 | 13 | jobs: 14 | publish: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | - name: Build the Docker image 21 | run: docker build . --tag image 22 | - name: Log into registry 23 | run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u wattlebird --password-stdin 24 | 25 | - name: Push image 26 | run: | 27 | IMAGE_ID=${{ github.repository }} 28 | # Strip git ref prefix from version 29 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 30 | # Strip "v" prefix from tag name 31 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 32 | # Use Docker `latest` tag convention 33 | [ "$VERSION" == "master" ] && VERSION=latest 34 | echo IMAGE_ID=$IMAGE_ID 35 | echo VERSION=$VERSION 36 | docker tag image $IMAGE_ID:$VERSION 37 | docker push $IMAGE_ID:$VERSION -------------------------------------------------------------------------------- /ClientApp/webpack/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 2 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 3 | const commonPaths = require('./paths'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | output: { 8 | filename: `${commonPaths.jsFolder}/[name].[hash].js`, 9 | path: commonPaths.outputPath, 10 | chunkFilename: '[name].[chunkhash].js', 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.css$/, 16 | use: [ 17 | MiniCssExtractPlugin.loader, 18 | { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: true, 22 | // modules: true, 23 | // camelCase: true, 24 | // localIdentName: '[local]___[hash:base64:5]', 25 | }, 26 | }, 27 | ], 28 | }, 29 | ], 30 | }, 31 | plugins: [ 32 | new CleanWebpackPlugin([commonPaths.outputPath.split('/').pop()], { 33 | root: commonPaths.root, 34 | }), 35 | new MiniCssExtractPlugin({ 36 | filename: `${commonPaths.cssFolder}/[name].css`, 37 | chunkFilename: '[id].css', 38 | }), 39 | ], 40 | devtool: 'source-map', 41 | }; 42 | -------------------------------------------------------------------------------- /ClientApp/webpack/webpack.common.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const convert = require('koa-connect'); 3 | const history = require('connect-history-api-fallback'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const commonPaths = require('./paths'); 6 | 7 | module.exports = { 8 | entry: commonPaths.entryPath, 9 | module: { 10 | rules: [ 11 | { 12 | enforce: 'pre', 13 | test: /\.(js|jsx)$/, 14 | loader: 'eslint-loader', 15 | exclude: /(node_modules)/, 16 | options: { 17 | emitWarning: process.env.NODE_ENV !== 'production', 18 | }, 19 | }, 20 | { 21 | test: /\.(js|jsx)$/, 22 | loader: 'babel-loader', 23 | exclude: /(node_modules)/, 24 | }, 25 | { 26 | test: /\.(png|jpg|gif|svg)$/, 27 | use: [ 28 | { 29 | loader: 'file-loader', 30 | options: { 31 | outputPath: commonPaths.imagesFolder, 32 | }, 33 | }, 34 | ], 35 | }, 36 | { 37 | test: /\.(woff2|ttf|woff|eot)$/, 38 | use: [ 39 | { 40 | loader: 'file-loader', 41 | options: { 42 | outputPath: commonPaths.fontsFolder, 43 | }, 44 | }, 45 | ], 46 | }, 47 | ], 48 | }, 49 | serve: { 50 | add: app => { 51 | app.use(convert(history())); 52 | }, 53 | content: commonPaths.entryPath, 54 | dev: { 55 | publicPath: commonPaths.outputPath, 56 | }, 57 | open: true, 58 | }, 59 | resolve: { 60 | modules: ['src', 'node_modules'], 61 | extensions: ['*', '.js', '.jsx', '.css', '.scss'], 62 | }, 63 | plugins: [ 64 | new webpack.ProgressPlugin(), 65 | new HtmlWebpackPlugin({ 66 | template: commonPaths.templatePath, 67 | }), 68 | ], 69 | }; 70 | -------------------------------------------------------------------------------- /ClientApp/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": ["react", "prettier"], 4 | "extends": ["airbnb", "plugin:prettier/recommended", "prettier/react"], 5 | "globals": { 6 | "React": true, 7 | "document": true, 8 | "window": true, 9 | "jQuery": true, 10 | "$": true, 11 | "localStorage": true, 12 | "fetch": true 13 | }, 14 | "root": true, 15 | "rules": { 16 | "indent": ["error", 2], 17 | "react/prefer-stateless-function": "warn", 18 | "react/self-closing-comp": [ 19 | "warn", 20 | { 21 | "component": true, 22 | "html": false 23 | } 24 | ], 25 | "react/sort-comp": [ 26 | 1, 27 | { 28 | "order": [ 29 | "static-methods", 30 | "lifecycle", 31 | "everything-else", 32 | "rendering" 33 | ], 34 | "groups": { 35 | "rendering": ["/^render.+$/", "render"] 36 | } 37 | } 38 | ], 39 | "react/require-default-props": 0, 40 | "jsx-a11y/href-no-hash": "off", 41 | "jsx-a11y/anchor-is-valid": ["warn", { "aspects": ["invalidHref"] }], 42 | "react/jsx-boolean-value": ["warn", "never"], 43 | "react/jsx-closing-bracket-location": ["warn", "after-props"], 44 | "react/jsx-curly-spacing": ["warn", "never"], 45 | "react/jsx-filename-extension": ["warn", { "extensions": [".jsx"] }], 46 | "react/jsx-first-prop-new-line": ["warn", "multiline"], 47 | "react/jsx-handler-names": [ 48 | "warn", 49 | { 50 | "eventHandlerPrefix": "handle", 51 | "eventHandlerPropPrefix": "on" 52 | } 53 | ], 54 | "react/jsx-indent": ["warn", 2], 55 | "react/jsx-key": "error", 56 | "react/jsx-wrap-multilines": ["warn"], 57 | "react/jsx-indent-props": 0, 58 | "no-trailing-spaces": [2, { "skipBlankLines": true }], 59 | "prefer-template": 0, 60 | "import/prefer-default-export": 0, 61 | "import/no-unresolved": 0, 62 | "import/no-extraneous-dependencies": 0, 63 | "import/extensions": 0, 64 | "babel/object-curly-spacing": 0, 65 | "prettier/prettier": "warn" 66 | }, 67 | "env": { 68 | "es6": true, 69 | "browser": true, 70 | "node": true 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ranking-showcase.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | true 6 | Latest 7 | false 8 | ClientApp\ 9 | $(DefaultItemExcludes);$(SpaRoot)node_modules\** 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | %(DistFiles.Identity) 45 | PreserveNewest 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Controllers/AnimeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using ranking_showcase.Service; 7 | 8 | namespace ranking_showcase.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | public class AnimeController : Controller 12 | { 13 | private RankingMgr rankingMgr; 14 | public AnimeController(RankingMgr rankingMgr) { 15 | this.rankingMgr = rankingMgr; 16 | } 17 | 18 | // Should match /api/anime/list, /api/anime/list?start=20&length=20 19 | [HttpGet("list")] 20 | public IActionResult ReadAll() 21 | { 22 | try{ 23 | int start = String.IsNullOrEmpty(Request.Query["start"]) ? 0 : Convert.ToInt32(Request.Query["start"]); 24 | int len = String.IsNullOrEmpty(Request.Query["length"]) ? 100000 : Convert.ToInt32(Request.Query["length"]); 25 | return Json(rankingMgr.readAll(start, len)); 26 | } catch (Exception e) { 27 | return BadRequest(e.Message); 28 | } 29 | 30 | } 31 | 32 | // Should match /api/anime/id/x 33 | [HttpGet("id/{id}")] 34 | public IActionResult ReadById(int id) { 35 | try { 36 | return Json(rankingMgr.readById(id)); 37 | } catch (Exception e) { 38 | return BadRequest(e.Message); 39 | } 40 | } 41 | 42 | // Should match /api/anime/rank/5, /api/anime/rank/5?source=original 43 | [HttpGet("rank/{rank}")] 44 | public IActionResult ReadByRank(int rank) { 45 | try { 46 | string source = Request.Query["source"]; 47 | if (source == "original") { 48 | return Json(rankingMgr.readByBgmrank(rank)); 49 | } 50 | else { 51 | return Json(rankingMgr.readByRank(rank)); 52 | } 53 | } catch (Exception e) { 54 | return BadRequest(e.Message); 55 | } 56 | } 57 | 58 | [HttpGet("search")] 59 | public IActionResult Search() { 60 | try { 61 | string q = Request.Query["q"]; 62 | if (!string.IsNullOrEmpty(q)) { 63 | return Json(rankingMgr.searchByName(q)); 64 | }else { 65 | return Json(new List{}); 66 | } 67 | } catch (Exception e) { 68 | return BadRequest(e.Message); 69 | } 70 | } 71 | 72 | [HttpGet("date")] 73 | public string Date() { 74 | return rankingMgr.readUpdateDate().ToString("yyyy-MM-dd"); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ClientApp/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { hot } from 'react-hot-loader'; 3 | import { Layout, Icon, Button } from 'antd'; 4 | import styled from 'styled-components'; 5 | import backgroundImg from 'assets/background2.jpg'; 6 | import Ranking from './components/Ranking'; 7 | 8 | const { Header, Content, Footer } = Layout; 9 | 10 | const CustomHeader = styled(Header)` 11 | color: rgba(255, 255, 255, 0.8); 12 | background-color: transparent; 13 | padding: 0 4em; 14 | height: 64px; 15 | display: flex; 16 | justify-content: space-between; 17 | align-items: center; 18 | font-size: 24px; 19 | `; 20 | 21 | const TitleBox = styled.div` 22 | margin: 8em auto; 23 | text-align: center; 24 | color: rgba(255, 255, 255, 0.9); 25 | `; 26 | 27 | const Title = styled.div` 28 | font-size: 36px; 29 | margin-bottom: 1em; 30 | `; 31 | 32 | const TitleNote = styled.span` 33 | font-size: 12px; 34 | `; 35 | 36 | const ButtonGroup = styled.div` 37 | font-size: 24px; 38 | display: flex; 39 | justify-content: center; 40 | `; 41 | 42 | const SubTitleButton = styled(Button)` 43 | margin-left: 8px; 44 | &.ant-btn { 45 | border-radius: 0; 46 | } 47 | :first-child { 48 | margin-left: 0; 49 | } 50 | `; 51 | 52 | const CustomFooter = styled(Footer)` 53 | &.ant-layout-footer { 54 | text-align: center; 55 | background-color: #020111; 56 | color: rgba(255, 255, 255, 0.8); 57 | } 58 | `; 59 | 60 | class App extends Component { 61 | constructor(props) { 62 | super(props); 63 | this.state = {}; 64 | } 65 | 66 | render() { 67 | return ( 68 | 74 | 75 |
Bangumi Research
76 |
77 | 78 |
79 |
80 | 81 | 82 | 83 | 某科学的 Bangumi 动画排名 84 | <TitleNote> v0.999</TitleNote> 85 | 86 | 87 | 使用说明 88 | 91 | GitHub 页面 92 | 93 | 94 | 95 | 96 | 97 | 98 | © 2018 Ronnie Wang, all rights reserved. Powered by React and .Net 99 | Core on Linux. 100 | 101 |
102 | ); 103 | } 104 | } 105 | 106 | export default hot(module)(App); 107 | -------------------------------------------------------------------------------- /ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rankit-showcase", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=development webpack-dev-server --open", 8 | "build": "cross-env NODE_ENV=production webpack", 9 | "format": "prettier --write 'packages/**/*.js'" 10 | }, 11 | "repository": "https://github.com/wattlebird/rankit-showcase", 12 | "author": "Ronnie Wang", 13 | "private": false, 14 | "engines": { 15 | "node": ">=8", 16 | "npm": ">=3" 17 | }, 18 | "dependencies": { 19 | "antd": "^3.10.7", 20 | "axios": "^0.18.0", 21 | "lodash": "4.17.15", 22 | "npm-check-updates": "^2.14.2", 23 | "prop-types": "15.6.2", 24 | "raf": "^3.4.1", 25 | "react": "16.6.0", 26 | "react-dom": "16.6.0", 27 | "react-hot-loader": "4.3.11", 28 | "react-router": "^4.3.1", 29 | "styled-components": "^4.1.1" 30 | }, 31 | "resolutions": { 32 | "babel-core": "7.0.0-bridge.0" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "7.1.2", 36 | "@babel/plugin-proposal-class-properties": "7.1.0", 37 | "@babel/plugin-proposal-decorators": "7.1.2", 38 | "@babel/plugin-proposal-export-namespace-from": "7.0.0", 39 | "@babel/plugin-proposal-function-sent": "7.1.0", 40 | "@babel/plugin-proposal-json-strings": "7.0.0", 41 | "@babel/plugin-proposal-numeric-separator": "7.0.0", 42 | "@babel/plugin-proposal-throw-expressions": "7.0.0", 43 | "@babel/plugin-syntax-dynamic-import": "7.0.0", 44 | "@babel/plugin-syntax-import-meta": "7.0.0", 45 | "@babel/plugin-transform-runtime": "7.1.0", 46 | "@babel/polyfill": "7.0.0", 47 | "@babel/preset-env": "7.1.0", 48 | "@babel/preset-react": "7.0.0", 49 | "@babel/register": "7.0.0", 50 | "@babel/runtime": "7.1.2", 51 | "babel-eslint": "10.0.1", 52 | "babel-loader": "8.0.4", 53 | "babel-plugin-import": "^1.11.0", 54 | "babel-plugin-lodash": "3.3.4", 55 | "browserslist": "4.3.4", 56 | "clean-webpack-plugin": "0.1.19", 57 | "connect-history-api-fallback": "^1.5.0", 58 | "cross-env": "5.2.0", 59 | "css-loader": "1.0.0", 60 | "eslint": "5.8.0", 61 | "eslint-config-airbnb": "17.1.0", 62 | "eslint-config-prettier": "3.1.0", 63 | "eslint-loader": "2.1.1", 64 | "eslint-plugin-import": "2.14.0", 65 | "eslint-plugin-jsx-a11y": "6.1.2", 66 | "eslint-plugin-prettier": "3.0.0", 67 | "eslint-plugin-react": "7.11.1", 68 | "eslint-watch": "4.0.2", 69 | "file-loader": "2.0.0", 70 | "html-webpack-plugin": "3.2.0", 71 | "identity-obj-proxy": "3.0.0", 72 | "jsdom": "^12.0.0", 73 | "koa-connect": "^2.0.1", 74 | "lint-staged": "7.3.0", 75 | "mini-css-extract-plugin": "0.4.4", 76 | "prettier": "1.14.3", 77 | "pretty-quick": "1.8.0", 78 | "sass-loader": "7.1.0", 79 | "style-loader": "0.23.1", 80 | "webpack": "4.23.1", 81 | "webpack-cli": "3.1.2", 82 | "webpack-dev-server": "3.10.1", 83 | "webpack-merge": "4.1.4" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.HttpsPolicy; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.FileProviders; 9 | using ranking_showcase.Service; 10 | 11 | namespace ranking_showcase 12 | { 13 | public class Startup 14 | { 15 | public Startup(IConfiguration configuration, IHostingEnvironment environment) 16 | { 17 | Configuration = configuration; 18 | this.environment = environment; 19 | } 20 | 21 | public IConfiguration Configuration { get; } 22 | private readonly IHostingEnvironment environment; 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 28 | var physicalProvider = environment.ContentRootFileProvider; 29 | services.AddSingleton(physicalProvider); 30 | services.AddSingleton(); 31 | services.AddHostedService(); 32 | services.AddCors(options => { 33 | options.AddPolicy("AllowAllOrigins", builder => builder.AllowAnyOrigin()); 34 | }); 35 | 36 | // In production, the React files will be served from this directory 37 | services.AddSpaStaticFiles(configuration => 38 | { 39 | configuration.RootPath = "ClientApp/build"; 40 | }); 41 | } 42 | 43 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 44 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 45 | { 46 | if (env.IsDevelopment()) 47 | { 48 | app.UseDeveloperExceptionPage(); 49 | } 50 | else 51 | { 52 | app.UseExceptionHandler("/Error"); 53 | //app.UseHsts(); 54 | } 55 | 56 | //app.UseHttpsRedirection(); 57 | app.UseStaticFiles(); 58 | app.UseSpaStaticFiles(); 59 | app.UseCors("AllowAllOrigins"); 60 | 61 | app.UseMvc(routes => 62 | { 63 | routes.MapRoute( 64 | name: "default", 65 | template: "{controller}/{action=Index}/{id?}"); 66 | }); 67 | 68 | app.UseSpa(spa => 69 | { 70 | spa.Options.SourcePath = "ClientApp"; 71 | 72 | if (env.IsDevelopment()) 73 | { 74 | spa.UseProxyToSpaDevelopmentServer("http://localhost:3000"); 75 | } 76 | }); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Services/RankingMgr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Microsoft.Extensions.FileProviders; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace ranking_showcase.Service { 9 | public class RankingItm { 10 | public int id {get;set;} 11 | public int rank {get;set;} 12 | public int bgmrank {get;set;} 13 | public String name {get;set;} 14 | } 15 | public class RankingMgr { 16 | private List rankingItms = new List(); 17 | private IFileProvider _fileProvider; 18 | private Regex rgx = new Regex(@"customrank_(\d+)_(\d+)_(\d+).csv", RegexOptions.Compiled); 19 | public string currentFile {get; private set;} 20 | public RankingMgr(IFileProvider fileProvider) { 21 | this._fileProvider = fileProvider; 22 | this.refresh(null); 23 | } 24 | 25 | public void refresh(string fileName) { 26 | if (String.IsNullOrEmpty(fileName)) return; 27 | var rankingFile = this._fileProvider.GetFileInfo(fileName); 28 | if (rankingFile.Exists) { 29 | this.rankingItms.Clear(); 30 | currentFile = fileName; 31 | using(StreamReader stm = new StreamReader(rankingFile.CreateReadStream())) { 32 | string line; 33 | stm.ReadLine(); 34 | while ((line = stm.ReadLine()) != null) { 35 | var rec = line.Split(',', 4); 36 | rankingItms.Add(new RankingItm{ 37 | id = Convert.ToInt32(rec[0]), 38 | rank = Convert.ToInt32(rec[1]), 39 | bgmrank = Convert.ToInt32(rec[2]), 40 | name = rec[3] 41 | }); 42 | } 43 | } 44 | } else { 45 | throw new FileNotFoundException($"{fileName} not found in current directory."); 46 | } 47 | 48 | } 49 | 50 | public IEnumerable readAll(int begin=0, int len=50000) { 51 | return rankingItms.Skip(begin).Take(len); 52 | } 53 | 54 | public RankingItm readById(int id) { 55 | return rankingItms.Where(itm => itm.id == id).FirstOrDefault(); 56 | } 57 | 58 | public RankingItm readByRank(int rank) { 59 | return rankingItms.Where(itm => itm.rank == rank).FirstOrDefault(); 60 | } 61 | 62 | public RankingItm readByBgmrank(int bgmrank) { 63 | return rankingItms.Where(itm => itm.bgmrank == bgmrank).FirstOrDefault(); 64 | } 65 | 66 | public IEnumerable searchByName(string name) { 67 | return rankingItms.Where(itm => itm.name.Contains(name)); 68 | } 69 | 70 | public DateTime readUpdateDate() { 71 | var matcher = this.rgx.Match(this.currentFile); 72 | if (matcher.Success) { 73 | return new DateTime(Convert.ToInt32(matcher.Groups[1].Value), Convert.ToInt32(matcher.Groups[2].Value), Convert.ToInt32(matcher.Groups[3].Value)); 74 | } else { 75 | return new DateTime(); 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /Services/FileSync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.FileProviders; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.WindowsAzure; 10 | using Microsoft.WindowsAzure.Storage; 11 | using Microsoft.WindowsAzure.Storage.File; 12 | using System.Text.RegularExpressions; 13 | 14 | namespace ranking_showcase.Service { 15 | public class FileSync : IHostedService, IDisposable { 16 | private Timer _timer; 17 | private Regex rgx = new Regex(@"customrank_(\d+)_(\d+)_(\d+).csv", RegexOptions.Compiled); 18 | private IFileProvider _fileProvider; 19 | private RankingMgr _rankingMgr; 20 | 21 | public FileSync( 22 | IFileProvider fileProvider, 23 | RankingMgr rankingMgr 24 | ) { 25 | this._fileProvider = fileProvider; 26 | this._rankingMgr = rankingMgr; 27 | } 28 | 29 | public Task StartAsync(CancellationToken cancellationToken) { 30 | _timer = new Timer(this.DownloadFileWrapper, null, TimeSpan.Zero, TimeSpan.FromHours(1)); 31 | return Task.CompletedTask; 32 | } 33 | 34 | public Task StopAsync(CancellationToken cancellationToken) { 35 | _timer?.Change(Timeout.Infinite, Timeout.Infinite); 36 | return Task.CompletedTask; 37 | } 38 | 39 | private void DownloadFileWrapper(object state) { 40 | this.DownloadFile().GetAwaiter().GetResult(); 41 | } 42 | 43 | private async Task DownloadFile() { 44 | CloudStorageAccount storageAccount = CloudStorageAccount.Parse( 45 | Environment.GetEnvironmentVariable("AZURE_STORAGE_IKELY_CONNECTIONSTRING") 46 | ); 47 | CloudFileClient fileClient = storageAccount.CreateCloudFileClient(); 48 | CloudFileShare share = fileClient.GetShareReference("bangumi-publish"); 49 | CloudFileDirectory dir = share.GetRootDirectoryReference().GetDirectoryReference("ranking"); 50 | FileContinuationToken token = null; 51 | DateTime date = new DateTime(2018, 1, 1); 52 | do 53 | { 54 | FileResultSegment resultSegment = await dir.ListFilesAndDirectoriesSegmentedAsync(token); 55 | token = resultSegment.ContinuationToken; 56 | foreach (IListFileItem rankItm in resultSegment.Results) 57 | { 58 | string fileName = rankItm.Uri.ToString(); 59 | var match = rgx.Match(fileName); 60 | if (match.Success) { 61 | DateTime newdate = new DateTime(Convert.ToInt32(match.Groups[1].Value), Convert.ToInt32(match.Groups[2].Value), Convert.ToInt32(match.Groups[3].Value)); 62 | if (newdate.CompareTo(date) > 0) { 63 | date = newdate; 64 | } 65 | } 66 | } 67 | } 68 | while (token != null); 69 | if (date.CompareTo(new DateTime(2018, 1, 1) ) != 0) { 70 | string rankingFile = $"customrank_{date.ToString("yyyy_MM_dd")}.csv"; 71 | CloudFile fr = dir.GetFileReference(rankingFile); 72 | // One should check whether there's an existing file 73 | bool ex = await fr.ExistsAsync(); 74 | if (ex) { 75 | await fr.DownloadToFileAsync(rankingFile, FileMode.Create); 76 | } 77 | if (this._rankingMgr.currentFile != rankingFile) { 78 | this._rankingMgr.refresh(rankingFile); 79 | } 80 | } 81 | } 82 | 83 | public void Dispose() { 84 | _timer?.Dispose(); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /ClientApp/src/components/Ranking.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, Table, Input, message } from 'antd'; 3 | import styled from 'styled-components'; 4 | import raf from 'raf'; 5 | import API from '../api'; 6 | 7 | const Content = styled(Card).attrs({ 8 | bordered: false, 9 | })` 10 | width: 67vw; 11 | &.ant-card { 12 | margin: 4em auto 2em; 13 | background-color: rgba(255, 255, 255, 0.85); 14 | color: #111517; 15 | } 16 | `; 17 | 18 | const SearchBar = styled.div` 19 | display: flex; 20 | align-items: baseline; 21 | margin-bottom: 1em; 22 | `; 23 | 24 | const DateLabel = styled.div` 25 | flex-shrink: 0; 26 | margin-left: 1em; 27 | `; 28 | 29 | const easeInOutCubic = (s, b, c, d) => { 30 | let t = s; 31 | const cc = c - b; 32 | t /= d / 2; 33 | if (t < 1) { 34 | return (cc / 2) * t * t * t + b; 35 | } 36 | t -= 2; 37 | return (cc / 2) * (t * t * t + 2) + b; 38 | }; 39 | 40 | class Ranking extends React.Component { 41 | state = { 42 | dataSource: [], 43 | updateDate: null, 44 | isLoading: true, 45 | name: null, 46 | }; 47 | 48 | componentDidMount() { 49 | this.setState({ 50 | isLoading: true, 51 | }); 52 | const promises = [API.ReadAll(), API.Date()]; 53 | Promise.all(promises).then( 54 | data => { 55 | this.setState({ 56 | dataSource: data[0], 57 | updateDate: data[1], 58 | isLoading: false, 59 | }); 60 | }, 61 | err => { 62 | this.setState({ 63 | isLoading: false, 64 | }); 65 | message.error('加载失败,可能是网络问题,请稍后重试。'); 66 | console.log(err); 67 | }, 68 | ); 69 | } 70 | 71 | onSearch = e => { 72 | this.setState({ 73 | name: e.target.value, 74 | }); 75 | }; 76 | 77 | onScrollToTop = () => { 78 | const self = document.getElementById('content'); 79 | const pageScrollTop = 80 | window.pageYOffset || document.documentElement.scrollTop; 81 | const contentScrollTop = self.offsetTop; 82 | if (contentScrollTop < pageScrollTop) { 83 | const start = Date.now(); 84 | const frame = () => { 85 | const current = Date.now(); 86 | const delta = current - start; 87 | const next = easeInOutCubic( 88 | delta, 89 | pageScrollTop, 90 | contentScrollTop, 91 | 450, 92 | ); 93 | document.body.scrollTop = next; 94 | document.documentElement.scrollTop = next; 95 | if (delta < 450) { 96 | raf(frame); 97 | } else { 98 | document.body.scrollTop = contentScrollTop; 99 | document.documentElement.scrollTop = contentScrollTop; 100 | } 101 | }; 102 | raf(frame); 103 | } 104 | }; 105 | 106 | render() { 107 | const { name, updateDate, dataSource, isLoading } = this.state; 108 | const parser = new DOMParser(); 109 | const columns = [ 110 | { 111 | title: '番剧', 112 | dataIndex: 'name', 113 | key: 'name', 114 | render: (itm, rec) => ( 115 | 120 | {parser.parseFromString(itm, 'text/html').body.textContent} 121 | 122 | ), 123 | }, 124 | { 125 | title: '本站排名', 126 | dataIndex: 'rank', 127 | key: 'rank', 128 | sorter: (a, b) => a.rank - b.rank, 129 | width: 120, 130 | }, 131 | { 132 | title: 'Bangumi 番组计划排名', 133 | dataIndex: 'bgmrank', 134 | key: 'bgmrank', 135 | sorter: (a, b) => a.bgmrank - b.bgmrank, 136 | width: 240, 137 | }, 138 | ]; 139 | return ( 140 | 141 | 142 | 146 | {updateDate && ( 147 | 148 | 排名更新时间: 149 | {updateDate} 150 | 151 | )} 152 | 153 | itm.name.includes(name)) 157 | : dataSource 158 | } 159 | columns={columns} 160 | loading={isLoading} 161 | pagination={{ 162 | pageSize: 20, 163 | pageSizeOptions: ['20', '50', '100'], 164 | showQuickJumper: true, 165 | showSizeChanger: true, 166 | onChange: this.onScrollToTop, 167 | }} 168 | /> 169 | 170 | ); 171 | } 172 | } 173 | 174 | export default Ranking; 175 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Properties/launchSettings.json 2 | 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | build/ 23 | bld/ 24 | bin/ 25 | Bin/ 26 | obj/ 27 | Obj/ 28 | out/ 29 | 30 | # Visual Studio 2015 cache/options directory 31 | .vs/ 32 | /wwwroot/dist/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | 155 | # Microsoft Azure Build Output 156 | csx/ 157 | *.build.csdef 158 | 159 | # Microsoft Azure Emulator 160 | ecf/ 161 | rcf/ 162 | 163 | # Microsoft Azure ApplicationInsights config file 164 | ApplicationInsights.config 165 | 166 | # Windows Store app package directory 167 | AppPackages/ 168 | BundleArtifacts/ 169 | 170 | # Visual Studio cache files 171 | # files ending in .cache can be ignored 172 | *.[Cc]ache 173 | # but keep track of directories ending in .cache 174 | !*.[Cc]ache/ 175 | 176 | # Others 177 | ClientBin/ 178 | ~$* 179 | *~ 180 | *.dbmdl 181 | *.dbproj.schemaview 182 | *.pfx 183 | *.publishsettings 184 | orleans.codegen.cs 185 | 186 | /node_modules 187 | 188 | # RIA/Silverlight projects 189 | Generated_Code/ 190 | 191 | # Backup & report files from converting an old project file 192 | # to a newer Visual Studio version. Backup files are not needed, 193 | # because we have git ;-) 194 | _UpgradeReport_Files/ 195 | Backup*/ 196 | UpgradeLog*.XML 197 | UpgradeLog*.htm 198 | 199 | # SQL Server files 200 | *.mdf 201 | *.ldf 202 | 203 | # Business Intelligence projects 204 | *.rdl.data 205 | *.bim.layout 206 | *.bim_*.settings 207 | 208 | # Microsoft Fakes 209 | FakesAssemblies/ 210 | 211 | # GhostDoc plugin setting file 212 | *.GhostDoc.xml 213 | 214 | # Node.js Tools for Visual Studio 215 | .ntvs_analysis.dat 216 | 217 | # Visual Studio 6 build log 218 | *.plg 219 | 220 | # Visual Studio 6 workspace options file 221 | *.opt 222 | 223 | # Visual Studio LightSwitch build output 224 | **/*.HTMLClient/GeneratedArtifacts 225 | **/*.DesktopClient/GeneratedArtifacts 226 | **/*.DesktopClient/ModelManifest.xml 227 | **/*.Server/GeneratedArtifacts 228 | **/*.Server/ModelManifest.xml 229 | _Pvt_Extensions 230 | 231 | # Paket dependency manager 232 | .paket/paket.exe 233 | 234 | # FAKE - F# Make 235 | .fake/ 236 | 237 | # Project related 238 | /.vscode/ 239 | *.csv 240 | !customrank.csv 241 | *.tsv 242 | 243 | # Docker 244 | docker-compose.yml --------------------------------------------------------------------------------