diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..f65827b --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,144 @@ +# ServiceStack mix GitHub Actions +`release.yml` generated from `x mix release-ecr-aws`, this template in designed to help with automating CI deployments to AWS ECS and dedicated AWS ECS cluster. +This is a cheap way to start without an AWS Application Load Balancer (ALB) and also be in a situation that will easier to add one once the web service needs additional scale or high availability. + +## Overview +`release.yml` is designed to work with a ServiceStack app templates deploying directly to a single server in a dedicated ECS cluster via templated GitHub Actions. + +## Setup +### Create unique ECS cluster +For this setup, it is best to create a separate cluster as cluster will only have the single instance in it running. +This pattern is to start from a good base with AWS ECS and automated CI deployment while avoiding the higher costs of needing to run an application load balancer (ALB). + +If/when you can justify the cost of an ALB for easier scaling and zero downtime deployment, the GitHub Action `release.yml` can be slightly modified to be used with a re-created or different ECS Service that is configured to be used with an Application Load Balancer and Target Group. + +### Elastic IP (optional) +The reason you might want to register this first is because we are only running one EC2 instance and hosting our own `nginx-proxy` on the same instance as the applications. +Since an `A` record will be pointing there, one advantage of not using an auto-assigned IP is that we can reassign the elastic IP if for what ever reason the instance goes down or is lost. + +## Launch to EC2 Instance +When launching the EC2 instance, you'll need to select an 'ECS optimized' AMI as the image used for your instance. +### Choose AMI +The easiest way to find the latest Amazon Linux 2 image for this is to go to the [AWS documentation for ECS-optimized AMIs and look up your region here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux). + +Using the AMI ID (starts with `ami-`) at the bottom, search in the 'Community AMIs' tab on the first step of the `Launch EC2 Instance` wizard. + +### Choose Instance Type +A t2.micro or larger will work fine, this pattern can be used to host multiple applications on the 1 server so if the number of applications gets larger, you might need a larger instance type. +> Note this pattern is suitable for testing prototypes or low traffic applications as it is cost effective and makes it easy to bundle multiple apps onto 1 EC2 instance. + +### Configure Instance +Under `IAM role`, use the `ecsInstanceRole`, if this is not available, see [AWS documentation for the process of checking if it exists and creating it if needed](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html). + +You will also want to add the following Userdata script (in the `Configure` step of the launch wizard) with your own `ECS_CLUSTER` value. This tells the ecs-agent running on the instance which ECS cluster the instance should join. + +```bash +#!/bin/bash +cat </etc/ecs/ecs.config +ECS_CLUSTER=redis-stack-overflows +ECS_AVAILABLE_LOGGING_DRIVERS=["awslogs", "syslog"] +ECS_ENABLE_CONTAINER_METADATA=true +EOS +``` + +Note down your cluster name as it will need to be used to create the cluster in ECS before it is visible. +See [`ECS Container Agent Configuration`](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-config.html) for more information. + +### Add Storage +The default of 30gb is fine but take into account how large/how many applications you'll have running. + +### Configure Security Groups +You'll want to expose at least ports 80 and 443. + +### Setup Docker-compose and nginx-proxy +To let your server handle multiple ServiceStack applications and automate the generation and management of TLS certificates, an additional docker-compose file is provided via the `x mix` template, `nginx-proxy-compose.yml`. This docker-compose file is ready to run and can be copied to the deployment server. +> This is done via docker-compose rather than via ECS for simplicity. + +For example, once copied to remote `~/nginx-proxy-compose.yml`, the following command can be run on the remote server. + +``` +docker-compose -f ~/nginx-proxy-compose.yml up -d +``` + +This will run an nginx reverse proxy along with a companion container that will watch for additional containers in the same docker network and attempt to initialize them with valid TLS certificates. + +## GitHub Repository setup +The `release.yml` assumes 6 secrets have been setup. + +- AWS_ACCESS_KEY_ID - AWS access key for programmatic access to AWS APIs. +- AWS_SECRET_ACCESS_KEY - AWS access secrets for programmatic access to AWS APIs. +- AWS_REGION - default region for AWS API calls. +- AWS_ECS_CLUSTER - Cluster name in ECS, this should match the value in your Userdata. +- HOST_DOMAIN - Domain/submain of your application, eg `redis-stack-overflow.example.com` . +- LETSENCRYPT_EMAIL - Email address, required for Let's Encrypt automated TLS certificates. + +These secrets are used to populate variables within GitHub Actions and other configuration files. + +For the AWS access, a separate user specifically for deploying via GitHub Actions should be used. + +The policies required for the complete initial setup will be: +- `AmazonEC2ContainerRegistryFullAccess` +- `AmazonECS_FullAccess` + +Once the application is successfully deployed the first time, reduced access for both ECR and ECS can be used instead. For application updates, the GitHub Action can use the following policy. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "ecr:GetRegistryPolicy", + "ecr:PutImageTagMutability", + "ecr:GetDownloadUrlForLayer", + "ecr:DescribeRegistry", + "ecr:GetAuthorizationToken", + "ecr:ListTagsForResource", + "ecr:UploadLayerPart", + "ecr:ListImages", + "ecr:PutImage", + "ecr:UntagResource", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload", + "ecr:DescribeImages", + "ecr:TagResource", + "ecr:DescribeRepositories", + "ecr:InitiateLayerUpload", + "ecr:BatchCheckLayerAvailability", + "ecr:ReplicateImage", + "ecr:GetRepositoryPolicy", + "ecs:SubmitTaskStateChange", + "ecs:UpdateContainerInstancesState", + "ecs:RegisterContainerInstance", + "ecs:DescribeTaskDefinition", + "ecs:DescribeClusters", + "ecs:ListServices", + "ecs:UpdateService", + "ecs:ListTasks", + "ecs:ListTaskDefinitionFamilies", + "ecs:RegisterTaskDefinition", + "ecs:SubmitContainerStateChange", + "ecs:StopTask", + "ecs:DescribeServices", + "ecs:ListContainerInstances", + "ecs:DescribeContainerInstances", + "ecs:DeregisterContainerInstance", + "ecs:TagResource", + "ecs:DescribeTasks", + "ecs:UntagResource", + "ecs:ListTaskDefinitions", + "ecs:ListClusters" + ], + "Resource": "*" + } + ] +} +``` +> Further permission reduction can be done by reducing what resources can be accessed. +> Application permissions can be controlled via `taskRoleArn`, see [AWS docs for details](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html). + +## What's the process of the `release.yml`? + +![](https://raw.githubusercontent.com/ServiceStack/docs/master/docs/images/mix/release-ecr-aws-diagram.png) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..82088dd --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,23 @@ +name: Build + +on: + pull_request: {} + push: + branches: + - '**' # matches every branch + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v2.0.0 + + - name: setup .net core + uses: actions/setup-dotnet@v1.7.2 + with: + dotnet-version: 6.0.100 + + - name: build + run: dotnet build + working-directory: ./src diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8bf8c5f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,94 @@ +name: Release +on: + release: + types: [published] +jobs: + push_to_ecr: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: repository name fix + run: echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Login to Amazon ECR + id: login_ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Create ECR repo if not exists. + env: + ECR_REPOSITORY: ${{ env.image_repository_name }} + run: aws ecr describe-repositories --repository-names ${ECR_REPOSITORY} || aws ecr create-repository --repository-name ${ECR_REPOSITORY} + + - name: Build and push to ECR + id: push_image_to_ecr + uses: docker/build-push-action@v2.2.2 + with: + file: Dockerfile + context: . + push: true + tags: ${{ steps.login_ecr.outputs.registry }}/${{ env.image_repository_name }}:${{ github.event.release.tag_name }} + + deploy_ecs: + needs: push_to_ecr + runs-on: ubuntu-20.04 + steps: + - name: checkout + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Login to Amazon ECR + id: login_ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Repository name fix and env values setup + run: | + echo "image_repository_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + echo "domain=${{ secrets.HOST_DOMAIN }}" >> $GITHUB_ENV + echo "letsencrypt_email=${{ secrets.LETSENCRYPT_EMAIL }}" >> $GITHUB_ENV + echo "app_name=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]' | cut -d'/' -f2)" >> $GITHUB_ENV + echo "cluster_name=${{ secrets.AWS_ECS_CLUSTER }}" >> $GITHUB_ENV + echo "image_url=${{ steps.login_ecr.outputs.registry }}/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]'):${{ github.event.release.tag_name }}" >> $GITHUB_ENV + echo "aws_region=${{ secrets.AWS_REGION }}" >> $GITHUB_ENV + + - name: Populate task definition template + uses: danielr1996/envsubst-action@1.0.0 + env: + RELEASE_VERSION: ${{ github.event.release.tag_name }} + APP_NAME: ${{ env.app_name }} + IMAGE_URL: ${{ env.image_url }} + HOST_DOMAIN: ${{ env.domain }} + LETSENCRYPT_EMAIL: ${{ env.letsencrypt_email }} + AWS_REGION: ${{ env.aws_region }} + CLUSTER_NAME: ${{ env.cluster_name }} + with: + input: deploy/task-definition-template.json + output: deploy/task-definition.json + + - name: Create task definition if doesn't exist + run: aws ecs describe-task-definition --task-definition ${{ env.app_name }} || aws ecs register-task-definition --cli-input-json file://deploy/task-definition.json + + - name: Create ECS Service if not exists. + run: aws ecs describe-services --cluster ${{ env.cluster_name }} --services ${{ env.app_name }} | jq '.services[0]' -e || aws ecs create-service --cluster ${{ env.cluster_name }} --service-name ${{ env.app_name }} --task-definition ${{ env.app_name }} --desired-count 1 + + - name: Deploy new revision of the task definition + uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + with: + task-definition: deploy/task-definition.json + service: ${{ env.app_name }} + cluster: ${{ env.cluster_name }} + force-new-deployment: true diff --git a/.gitignore b/.gitignore index 748683d..a8fa6e0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,23 +4,34 @@ # User-specific files *.suo *.user +*.userosscache *.sln.docstates +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ +[Rr]eleases/ x64/ -build/ +x86/ bld/ [Bb]in/ [Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -#NUNIT +# NUNIT *.VisualState.xml TestResult.xml @@ -29,6 +40,12 @@ TestResult.xml [Rr]eleasePS/ dlldata.c +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ +*.lock.json + *_i.c *_p.c *_i.h @@ -61,14 +78,18 @@ _Chutzpah* ipch/ *.aps *.ncb +*.opendb *.opensdf *.sdf *.cachefile +*.VC.db +*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx +*.sap # TFS 2012 Local Workspace $tf/ @@ -81,7 +102,7 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -# JustCode is a .NET coding addin-in +# JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in @@ -90,10 +111,14 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover +# Visual Studio code coverage results +*.coverage +*.coveragexml + # NCrunch -*.ncrunch* _NCrunch_* .*crunch*.local.xml +nCrunchTemp_* # MightyMoose *.mm.* @@ -121,41 +146,70 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml - -# NuGet Packages Directory -packages/ -## TODO: If the tool you use requires repositories.config uncomment the next line -#!packages/repositories.config - -# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets -# This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) -!packages/build/ - -# Windows Azure Build Output +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output csx/ *.build.csdef -# Windows Store app package directory +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ # Others -sql/ -*.Cache ClientBin/ -[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview +*.jfm *.pfx *.publishsettings node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ # RIA/Silverlight projects Generated_Code/ -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML @@ -172,3 +226,44 @@ UpgradeLog*.htm # Microsoft Fakes FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..70d7f47 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,46 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/src/RedisStackOverflow/bin/Debug/netcoreapp1.1/RedisStackOverflow.dll", + "args": [], + "cwd": "${workspaceFolder}/src/RedisStackOverflow", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart", + "launchBrowser": { + "enabled": true, + "args": "${auto-detect-url}", + "windows": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}" + }, + "osx": { + "command": "open" + }, + "linux": { + "command": "xdg-open" + } + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..0b4a0ee --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "taskName": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/RedisStackOverflow/RedisStackOverflow.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8e307aa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/dotnet/sdk:6.0-focal AS build +WORKDIR /app + +COPY ./src . +RUN dotnet restore + +WORKDIR /app/RedisStackOverflow +RUN dotnet publish -c release -o /out --no-restore + +FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal AS runtime +WORKDIR /app +COPY --from=build /out . +ENTRYPOINT ["dotnet", "RedisStackOverflow.dll"] diff --git a/README.md b/README.md index 7daa3cc..380e006 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## [Redis StackOverflow](https://github.com/ServiceStackApps/RedisStackOverflow) +## [Redis StackOverflow](https://github.com/NetCoreApps/RedisStackOverflow) > Mini StackOverflow Single Page App built using just ServiceStack + Redis @@ -6,10 +6,10 @@ #### Features - - [1 Page jQuery](https://github.com/ServiceStackApps/RedisStackOverflow/blob/master/src/RedisStackOverflow/RedisStackOverflow/default.htm) - - [1 Redis Repository](https://github.com/ServiceStackApps/RedisStackOverflow/blob/master/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/IRepository.cs) + - [1 Page jQuery](https://github.com/NetCoreApps/RedisStackOverflow/blob/master/src/RedisStackOverflow/wwwroot/default.htm) + - [1 Redis Repository](https://github.com/NetCoreApps/RedisStackOverflow/blob/master/src/RedisStackOverflow.ServiceInterface/IRepository.cs) - [ServiceStack.Redis C# Client](https://github.com/ServiceStack/ServiceStack.Redis) Try it out live at: [redisstackoverflow.servicestack.net](http://redisstackoverflow.servicestack.net) -Follow [@ServiceStack](http://twitter.com) or [+ServiceStack](https://plus.google.com/u/0/communities/112445368900682590445) for updates. +Follow [@ServiceStack](http://twitter.com) for updates. diff --git a/deploy/nginx-proxy-compose.yml b/deploy/nginx-proxy-compose.yml new file mode 100644 index 0000000..11ba1a9 --- /dev/null +++ b/deploy/nginx-proxy-compose.yml @@ -0,0 +1,45 @@ +version: '2' + +services: + nginx-proxy: + image: jwilder/nginx-proxy + container_name: nginx-proxy + restart: always + ports: + - "80:80" + - "443:443" + volumes: + - conf:/etc/nginx/conf.d + - vhost:/etc/nginx/vhost.d + - html:/usr/share/nginx/html + - dhparam:/etc/nginx/dhparam + - certs:/etc/nginx/certs:ro + - /var/run/docker.sock:/tmp/docker.sock:ro + network_mode: bridge + + letsencrypt: + image: jrcs/letsencrypt-nginx-proxy-companion + container_name: nginx-proxy-le + restart: always + environment: + - DEFAULT_EMAIL=you@example.com + volumes_from: + - nginx-proxy + volumes: + - certs:/etc/nginx/certs:rw + - acme:/etc/acme.sh + - /var/run/docker.sock:/var/run/docker.sock:ro + network_mode: bridge + +volumes: + conf: + vhost: + html: + dhparam: + certs: + acme: + +networks: + default: + external: + name: webproxy \ No newline at end of file diff --git a/deploy/task-definition-template.json b/deploy/task-definition-template.json new file mode 100644 index 0000000..9d1d5a7 --- /dev/null +++ b/deploy/task-definition-template.json @@ -0,0 +1,67 @@ +{ + "family": "${APP_NAME}", + "requiresCompatibilities": [ + "EC2" + ], + "networkMode": "bridge", + "containerDefinitions": [ + { + "portMappings": [ + { + "protocol": "tcp", + "containerPort": 80 + } + ], + "environment": [ + { + "name": "VIRTUAL_HOST", + "value": "${HOST_DOMAIN}" + }, + { + "name": "LETSENCRYPT_HOST", + "value": "${HOST_DOMAIN}" + }, + { + "name": "LETSENCRYPT_EMAIL", + "value": "${LETSENCRYPT_EMAIL}" + }, + { + "name": "APP_VERSION", + "value": "${RELEASE_VERSION}" + }, + { + "name": "REDIS_HOST", + "value": "redis" + } + ], + "mountPoints": [], + "memoryReservation": 128, + "volumesFrom": [], + "image": "${IMAGE_URL}", + "essential": true, + "name": "${APP_NAME}", + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "${CLUSTER_NAME}-${APP_NAME}", + "awslogs-region": "${AWS_REGION}", + "awslogs-create-group": "true" + } + }, + "links": ["redis"] + }, + { + "portMappings": [ + { + "protocol": "tcp", + "containerPort": 6379 + } + ], + "cpu": 0, + "memoryReservation": 32, + "image": "redis", + "essential": true, + "name": "redis" + } + ] +} diff --git a/local-compose.yml b/local-compose.yml new file mode 100644 index 0000000..fc80b4b --- /dev/null +++ b/local-compose.yml @@ -0,0 +1,14 @@ +version: "3.9" + +services: + redis: + image: redis + + web: + build: . + environment: + - REDIS_HOST=redis + ports: + - "8080:80" + depends_on: + - redis \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/AnswersService.cs b/src/RedisStackOverflow.ServiceInterface/AnswersService.cs similarity index 93% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/AnswersService.cs rename to src/RedisStackOverflow.ServiceInterface/AnswersService.cs index 7599c99..d0bb2d4 100644 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/AnswersService.cs +++ b/src/RedisStackOverflow.ServiceInterface/AnswersService.cs @@ -12,7 +12,7 @@ public class AnswersService : Service /// /// Gets or sets the repository. The built-in IoC used with ServiceStack autowires this property. /// - public IRepository Repository { get; set; } + public IRedisRepository Repository { get; set; } public void Post(Answers request) { diff --git a/src/RedisStackOverflow.ServiceInterface/AppExtensions.cs b/src/RedisStackOverflow.ServiceInterface/AppExtensions.cs new file mode 100644 index 0000000..f4ea808 --- /dev/null +++ b/src/RedisStackOverflow.ServiceInterface/AppExtensions.cs @@ -0,0 +1,23 @@ +using ServiceStack.Auth; +using ServiceStack.Configuration; + +namespace RedisStackOverflow; + +public static class AppExtensions +{ + public static void CreateUsers(this IAuthRepository authRepo) + { + CreateUser(authRepo, "admin@email.com", "Admin User", "p@55wOrd", roles: new[] { RoleNames.Admin }); + } + + // Add initial Users to the configured Auth Repository + public static void CreateUser(this IAuthRepository authRepo, string email, string name, string password, string[] roles) + { + if (authRepo.GetUserAuthByUserName(email) == null) + { + var newAdmin = new UserAuth { Email = email, DisplayName = name }; + var user = authRepo.CreateUserAuth(newAdmin, password); + authRepo.AssignRoles(user, roles); + } + } +} diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/IRepository.cs b/src/RedisStackOverflow.ServiceInterface/IRedisRepository.cs similarity index 56% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/IRepository.cs rename to src/RedisStackOverflow.ServiceInterface/IRedisRepository.cs index a966b8b..98c92fc 100644 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/IRepository.cs +++ b/src/RedisStackOverflow.ServiceInterface/IRedisRepository.cs @@ -7,7 +7,7 @@ namespace RedisStackOverflow.ServiceInterface { - public interface IRepository + public interface IRedisRepository { User GetOrCreateUser(User user); @@ -48,39 +48,39 @@ public interface IRepository void DeleteAnswer(long questionId, long answerId); } - public class Repository : IRepository + public class RedisRepository : IRedisRepository { //Definition of all the redis keys that are used for indexes static class TagIndex { - public static string Questions(string tag) { return "urn:tags>q:" + tag.ToLower(); } - public static string All { get { return "urn:tags"; } } + public static string Questions(string tag) => "urn:tags>q:" + tag.ToLower(); + public static string All => "urn:tags"; } static class QuestionUserIndex { - public static string UpVotes(long questionId) { return "urn:q>user+:" + questionId; } - public static string DownVotes(long questionId) { return "urn:q>user-:" + questionId; } + public static string UpVotes(long questionId) => "urn:q>user+:" + questionId; + public static string DownVotes(long questionId) => "urn:q>user-:" + questionId; } static class UserQuestionIndex { - public static string Questions(long userId) { return "urn:user>q:" + userId; } - public static string UpVotes(long userId) { return "urn:user>q+:" + userId; } - public static string DownVotes(long userId) { return "urn:user>q-:" + userId; } + public static string Questions(long userId) => "urn:user>q:" + userId; + public static string UpVotes(long userId) => "urn:user>q+:" + userId; + public static string DownVotes(long userId) => "urn:user>q-:" + userId; } static class AnswerUserIndex { - public static string UpVotes(long answerId) { return "urn:a>user+:" + answerId; } - public static string DownVotes(long answerId) { return "urn:a>user-:" + answerId; } + public static string UpVotes(long answerId) => "urn:a>user+:" + answerId; + public static string DownVotes(long answerId) => "urn:a>user-:" + answerId; } static class UserAnswerIndex { - public static string Answers(long userId) { return "urn:user>a:" + userId; } - public static string UpVotes(long userId) { return "urn:user>a+:" + userId; } - public static string DownVotes(long userId) { return "urn:user>a-:" + userId; } + public static string Answers(long userId) => "urn:user>a:" + userId; + public static string UpVotes(long userId) => "urn:user>a+:" + userId; + public static string DownVotes(long userId) => "urn:user>a-:" + userId; } /// @@ -88,7 +88,7 @@ static class UserAnswerIndex /// IRedisClientsManager RedisManager { get; set; } - public Repository(IRedisClientsManager redisManager) + public RedisRepository(IRedisClientsManager redisManager) { RedisManager = redisManager; } @@ -100,40 +100,36 @@ public User GetOrCreateUser(User user) var userIdAliasKey = "id:User:DisplayName:" + user.DisplayName.ToLower(); - using (var redis = RedisManager.GetClient()) - { - //Get a typed version of redis client that works with - var redisUsers = redis.As(); + using var redis = RedisManager.GetClient(); + //Get a typed version of redis client that works with + var redisUsers = redis.As(); - //Find user by DisplayName if exists - var userKey = redis.GetValue(userIdAliasKey); - if (userKey != null) - return redisUsers.GetValue(userKey); + //Find user by DisplayName if exists + var userKey = redis.GetValue(userIdAliasKey); + if (userKey != null) + return redisUsers.GetValue(userKey); - //Generate Id for New User - if (user.Id == default(long)) - user.Id = redisUsers.GetNextSequence(); + //Generate Id for New User + if (user.Id == default(long)) + user.Id = redisUsers.GetNextSequence(); - redisUsers.Store(user); + redisUsers.Store(user); - //Save reference to User key using the DisplayName alias - redis.SetEntry(userIdAliasKey, user.CreateUrn()); + //Save reference to User key using the DisplayName alias + redis.SetValue(userIdAliasKey, user.CreateUrn()); - return redisUsers.GetById(user.Id); - } + return redisUsers.GetById(user.Id); } public UserStat GetUserStats(long userId) { - using (var redis = RedisManager.GetClient()) + using var redis = RedisManager.GetClient(); + return new UserStat { - return new UserStat - { - UserId = userId, - QuestionsCount = redis.GetSetCount(UserQuestionIndex.Questions(userId)), - AnswersCount = redis.GetSetCount(UserAnswerIndex.Answers(userId)), - }; - } + UserId = userId, + QuestionsCount = redis.GetSetCount(UserQuestionIndex.Questions(userId)), + AnswersCount = redis.GetSetCount(UserAnswerIndex.Answers(userId)), + }; } public List GetAllQuestions() @@ -143,35 +139,29 @@ public List GetAllQuestions() public List GetRecentQuestionResults(int skip, int take) { - using (var redis = RedisManager.GetReadOnlyClient()) - { - return ToQuestionResults(redis.As().GetLatestFromRecentsList(skip, take)); - } + using var redis = RedisManager.GetReadOnlyClient(); + return ToQuestionResults(redis.As().GetLatestFromRecentsList(skip, take)); } public List GetQuestionsByUser(long userId) { - using (var redis = RedisManager.GetReadOnlyClient()) - { - var questionIds = redis.GetAllItemsFromSet(UserQuestionIndex.Questions(userId)); - var questions = redis.As().GetByIds(questionIds); - return ToQuestionResults(questions); - } + using var redis = RedisManager.GetReadOnlyClient(); + var questionIds = redis.GetAllItemsFromSet(UserQuestionIndex.Questions(userId)); + var questions = redis.As().GetByIds(questionIds); + return ToQuestionResults(questions); } public List GetQuestionsTaggedWith(string tagName) { - using (var redis = RedisManager.GetReadOnlyClient()) - { - var questionIds = redis.GetAllItemsFromSet(TagIndex.Questions(tagName)); - var questions = redis.As().GetByIds(questionIds); - return ToQuestionResults(questions); - } + using var redis = RedisManager.GetReadOnlyClient(); + var questionIds = redis.GetAllItemsFromSet(TagIndex.Questions(tagName)); + var questions = redis.As().GetByIds(questionIds); + return ToQuestionResults(questions); } private List ToQuestionResults(IEnumerable questions) { - var uniqueUserIds = questions.Map(x => x.UserId).ToHashSet(); + var uniqueUserIds = questions.Map(x => x.UserId).ToSet(); var usersMap = GetUsersByIds(uniqueUserIds).ToDictionary(x => x.Id); var results = questions.Map(x => new QuestionResult { Question = x }); @@ -206,52 +196,48 @@ private List ToQuestionResults(IEnumerable questions) /// public void DeleteQuestion(long questionId) { - using (var redis = RedisManager.GetClient()) - { - var redisQuestions = redis.As(); + using var redis = RedisManager.GetClient(); + var redisQuestions = redis.As(); - var question = redisQuestions.GetById(questionId); - if (question == null) return; + var question = redisQuestions.GetById(questionId); + if (question == null) return; - //decrement score in tags list - question.Tags.ForEach(tag => redis.IncrementItemInSortedSet(TagIndex.All, tag, -1)); + //decrement score in tags list + question.Tags.ForEach(tag => redis.IncrementItemInSortedSet(TagIndex.All, tag, -1)); - //remove all related answers - redisQuestions.DeleteRelatedEntities(questionId); + //remove all related answers + redisQuestions.DeleteRelatedEntities(questionId); - //remove this question from user index - redis.RemoveItemFromSet(UserQuestionIndex.Questions(question.UserId), questionId.ToString()); + //remove this question from user index + redis.RemoveItemFromSet(UserQuestionIndex.Questions(question.UserId), questionId.ToString()); - //remove tag => questions index for each tag - question.Tags.ForEach(tag => redis.RemoveItemFromSet(TagIndex.Questions(tag), questionId.ToString())); + //remove tag => questions index for each tag + question.Tags.ForEach(tag => redis.RemoveItemFromSet(TagIndex.Questions(tag), questionId.ToString())); - redisQuestions.DeleteById(questionId); - } + redisQuestions.DeleteById(questionId); } public void StoreQuestion(Question question) { - using (var redis = RedisManager.GetClient()) - { - var redisQuestions = redis.As(); + using var redis = RedisManager.GetClient(); + var redisQuestions = redis.As(); - if (question.Tags == null) question.Tags = new List(); - if (question.Id == default(long)) - { - question.Id = redisQuestions.GetNextSequence(); - question.CreatedDate = DateTime.UtcNow; + if (question.Tags == null) question.Tags = new List(); + if (question.Id == default(long)) + { + question.Id = redisQuestions.GetNextSequence(); + question.CreatedDate = DateTime.UtcNow; - //Increment the popularity for each new question tag - question.Tags.ForEach(tag => redis.IncrementItemInSortedSet(TagIndex.All, tag, 1)); - } + //Increment the popularity for each new question tag + question.Tags.ForEach(tag => redis.IncrementItemInSortedSet(TagIndex.All, tag, 1)); + } - redisQuestions.Store(question); - redisQuestions.AddToRecentsList(question); - redis.AddItemToSet(UserQuestionIndex.Questions(question.UserId), question.Id.ToString()); + redisQuestions.Store(question); + redisQuestions.AddToRecentsList(question); + redis.AddItemToSet(UserQuestionIndex.Questions(question.UserId), question.Id.ToString()); - //Populate tag => questions index for each tag - question.Tags.ForEach(tag => redis.AddItemToSet(TagIndex.Questions(tag), question.Id.ToString())); - } + //Populate tag => questions index for each tag + question.Tags.ForEach(tag => redis.AddItemToSet(TagIndex.Questions(tag), question.Id.ToString())); } /// @@ -261,41 +247,35 @@ public void StoreQuestion(Question question) /// public void DeleteAnswer(long questionId, long answerId) { - using (var redis = RedisManager.GetClient()) - { - var answer = redis.As().GetRelatedEntities(questionId).FirstOrDefault(x => x.Id == answerId); - if (answer == null) return; + using var redis = RedisManager.GetClient(); + var answer = redis.As().GetRelatedEntities(questionId).FirstOrDefault(x => x.Id == answerId); + if (answer == null) return; - redis.As().DeleteRelatedEntity(questionId, answerId); + redis.As().DeleteRelatedEntity(questionId, answerId); - //remove user => answer index - redis.RemoveItemFromSet(UserAnswerIndex.Answers(answer.UserId), answerId.ToString()); - } + //remove user => answer index + redis.RemoveItemFromSet(UserAnswerIndex.Answers(answer.UserId), answerId.ToString()); } public void StoreAnswer(Answer answer) { - using (var redis = RedisManager.GetClient()) + using var redis = RedisManager.GetClient(); + if (answer.Id == default) { - if (answer.Id == default(long)) - { - answer.Id = redis.As().GetNextSequence(); - answer.CreatedDate = DateTime.UtcNow; - } - - //Store as a 'Related Answer' to the parent Question - redis.As().StoreRelatedEntities(answer.QuestionId, answer); - //Populate user => answer index - redis.AddItemToSet(UserAnswerIndex.Answers(answer.UserId), answer.Id.ToString()); + answer.Id = redis.As().GetNextSequence(); + answer.CreatedDate = DateTime.UtcNow; } + + //Store as a 'Related Answer' to the parent Question + redis.As().StoreRelatedEntities(answer.QuestionId, answer); + //Populate user => answer index + redis.AddItemToSet(UserAnswerIndex.Answers(answer.UserId), answer.Id.ToString()); } public List GetAnswersForQuestion(long questionId) { - using (var redis = RedisManager.GetClient()) - { - return redis.As().GetRelatedEntities(questionId); - } + using var redis = RedisManager.GetClient(); + return redis.As().GetRelatedEntities(questionId); } public void VoteQuestionUp(long userId, long questionId) @@ -365,7 +345,7 @@ public QuestionResult GetQuestion(long questionId) var result = ToQuestionResults(new[] { question })[0]; var answers = GetAnswersForQuestion(questionId); - var uniqueUserIds = answers.ConvertAll(x => x.UserId).ToHashSet(); + var uniqueUserIds = answers.ConvertAll(x => x.UserId).ToSet(); var usersMap = GetUsersByIds(uniqueUserIds).ToDictionary(x => x.Id); result.Answers = answers.ConvertAll(answer => @@ -381,39 +361,32 @@ public List GetUsersByIds(IEnumerable userIds) public QuestionStat GetQuestionStats(long questionId) { - using (var redis = RedisManager.GetReadOnlyClient()) - { - var result = new QuestionStat - { - VotesUpCount = redis.GetSetCount(QuestionUserIndex.UpVotes(questionId)), - VotesDownCount = redis.GetSetCount(QuestionUserIndex.DownVotes(questionId)) - }; - result.VotesTotal = result.VotesUpCount - result.VotesDownCount; - return result; - } + using var redis = RedisManager.GetReadOnlyClient(); + var result = new QuestionStat { + VotesUpCount = redis.GetSetCount(QuestionUserIndex.UpVotes(questionId)), + VotesDownCount = redis.GetSetCount(QuestionUserIndex.DownVotes(questionId)) + }; + result.VotesTotal = result.VotesUpCount - result.VotesDownCount; + return result; } public List GetTagsByPopularity(int skip, int take) { - using (var redis = RedisManager.GetReadOnlyClient()) - { - var tagEntries = redis.GetRangeWithScoresFromSortedSetDesc(TagIndex.All, skip, take); - var tags = tagEntries.Map(kvp => new Tag { Name = kvp.Key, Score = (int)kvp.Value }); - return tags; - } + using var redis = RedisManager.GetReadOnlyClient(); + var tagEntries = redis.GetRangeWithScoresFromSortedSetDesc(TagIndex.All, skip, take); + var tags = tagEntries.Map(kvp => new Tag { Name = kvp.Key, Score = (int)kvp.Value }); + return tags; } public SiteStats GetSiteStats() { - using (var redis = RedisManager.GetClient()) + using var redis = RedisManager.GetClient(); + return new SiteStats { - return new SiteStats - { - QuestionsCount = redis.As().TypeIdsSet.Count, - AnswersCount = redis.As().TypeIdsSet.Count, - TopTags = GetTagsByPopularity(0, 10) - }; - } + QuestionsCount = redis.As().TypeIdsSet.Count, + AnswersCount = redis.As().TypeIdsSet.Count, + TopTags = GetTagsByPopularity(0, 10) + }; } } } \ No newline at end of file diff --git a/src/RedisStackOverflow.ServiceInterface/Properties/AssemblyInfo.cs b/src/RedisStackOverflow.ServiceInterface/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..96039c0 --- /dev/null +++ b/src/RedisStackOverflow.ServiceInterface/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RedisStackOverflow.ServiceInterface")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ff2b473f-40bb-42eb-966d-b32bef951d02")] diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/QuestionsService.cs b/src/RedisStackOverflow.ServiceInterface/QuestionsService.cs similarity index 96% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/QuestionsService.cs rename to src/RedisStackOverflow.ServiceInterface/QuestionsService.cs index a0bfc4e..65ff8a1 100644 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/QuestionsService.cs +++ b/src/RedisStackOverflow.ServiceInterface/QuestionsService.cs @@ -11,7 +11,7 @@ public class QuestionsService : Service /// /// Gets or sets the repository. The built-in IoC used with ServiceStack autowires this property. /// - public IRepository Repository { get; set; } + public IRedisRepository Repository { get; set; } public object Get(Questions request) { diff --git a/src/RedisStackOverflow.ServiceInterface/RedisStackOverflow.ServiceInterface.csproj b/src/RedisStackOverflow.ServiceInterface/RedisStackOverflow.ServiceInterface.csproj new file mode 100755 index 0000000..03e6813 --- /dev/null +++ b/src/RedisStackOverflow.ServiceInterface/RedisStackOverflow.ServiceInterface.csproj @@ -0,0 +1,25 @@ + + + + net6.0 + RedisStackOverflow.ServiceInterface + RedisStackOverflow.ServiceInterface + false + false + false + + + + + + + + + + + + + + + + diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/ResetService.cs b/src/RedisStackOverflow.ServiceInterface/ResetService.cs similarity index 86% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/ResetService.cs rename to src/RedisStackOverflow.ServiceInterface/ResetService.cs index 9311a93..c9aff0f 100644 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/ResetService.cs +++ b/src/RedisStackOverflow.ServiceInterface/ResetService.cs @@ -18,16 +18,16 @@ public class ResetService : Service /// /// Gets or sets the repository. The built-in IoC used with ServiceStack autowires this property. /// - public IRepository Repository { get; set; } + public IRedisRepository Repository { get; set; } - private static readonly Dictionary JavascriptQuestions = new Dictionary { + private static readonly Dictionary JavascriptQuestions = new() { {"a javascript string problem?","i have this script, but i the result.bgimage is not showing up, i think JS thinks its a string rather than variable!!! how can i solve this problem? thanks"}, {"Control events of Object (HTML) by JavaScript","Is it possible to control an object that contains a swf that streams music, I want to be able to pause it by JS, but I don't know if that's possible or not."}, {"Javascript function not working with an array","i have this function which loops through an array of check boxes checking if the boxes value is equal to something in a text box, i dont know whats wrong."}, - {"Prototypal programming in Python","Javascript uses a prototype-based model for its objects. Nevertheless, the language is very flexible, and it is easy to write in a few lines functions which replace other kind on constructs. For instance, one can make a class function, emulating the standard class behaviour, including inheritance or private members. Or one can mimcìic functional tools by writing, for instance, a curry function which will take a function and some of its arguments and return the partially applied function."}, + {"Prototypal programming in Python","Javascript uses a prototype-based model for its objects. Nevertheless, the language is very flexible, and it is easy to write in a few lines functions which replace other kind on constructs. For instance, one can make a class function, emulating the standard class behaviour, including inheritance or private members. Or one can mimcìic functional tools by writing, for instance, a curry function which will take a function and some of its arguments and return the partially applied function."}, }; - static readonly Dictionary RestQuestions = new Dictionary { + static readonly Dictionary RestQuestions = new() { {"How do I implement login in a RESTful web service?","I am building a web application with a services layer. The services layer is going to be built using a RESTful design. The thinking is that some time in the future we may build other applications (iPhone, Android, etc.) that use the same services layer as the web application. My question is this - how do I implement login? I think I am having trouble moving from a more traditional verb based design to a resource based design. If I was building this with SOAP I would probably have a method called Login. In REST I should have a resource."}, {"Framework for providing API access to website?","We have a website that we want to provide web based API access to to other sites. It may end up being a REST based API, but I'm not sure yet. It needs to be accessible from a Drupal module, but we want to built the API to be scalable so that we can access the site's data and functionality from other environments such as joomla, wordpress, other non-php languages, etc. I am looking for a robust/stable PHP based framework that allows me to create such APIs - can folks suggest something that meets the criteria?"}, {"Does HATEOAS imply query strings are not RESTful?","Does the HATEOAS (hypermedia as the engine of app state) recommendation imply that query strings are not RESTful?"}, @@ -46,11 +46,8 @@ public Question ToQuestion(string title, string body, List tags) public object Get(Reset request) { - //Uncomment if you want this feature - //throw new NotSupportedException("Disabling for Demo site. Based on the XSS attacks I know it will only be a matter of time before someone pulls the trigger."); - - var questionsd = new Dictionary(RestQuestions); - JavascriptQuestions.ForEach(questionsd.Add); + var restQuestions = new Dictionary(RestQuestions); + JavascriptQuestions.ForEach(restQuestions.Add); var questions = new List(); questions.AddRange(RestQuestions.Map(kvp => ToQuestion(kvp.Key, kvp.Value, new List { "rest", "http" }))); @@ -63,8 +60,10 @@ public object Get(Reset request) questions.ForEach(q => q.UserId = mythz.Id); questions.ForEach(q => Repository.StoreQuestion(q)); - - return new ResetResponse(); + + AuthRepository.CreateUsers(); + + return HttpResult.Redirect("/"); } } } \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/StatsService.cs b/src/RedisStackOverflow.ServiceInterface/StatsService.cs similarity index 91% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/StatsService.cs rename to src/RedisStackOverflow.ServiceInterface/StatsService.cs index 83b0cab..89bb5df 100644 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/StatsService.cs +++ b/src/RedisStackOverflow.ServiceInterface/StatsService.cs @@ -11,7 +11,7 @@ public class StatsService : Service /// /// Gets or sets the repository. The built-in IoC used with ServiceStack autowires this property. /// - public IRepository Repository { get; set; } + public IRedisRepository Repository { get; set; } public StatsResponse Get(Stats request) { diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/UserService.cs b/src/RedisStackOverflow.ServiceInterface/UserService.cs similarity index 96% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/UserService.cs rename to src/RedisStackOverflow.ServiceInterface/UserService.cs index 6e7877b..f83feb0 100644 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/UserService.cs +++ b/src/RedisStackOverflow.ServiceInterface/UserService.cs @@ -11,7 +11,7 @@ public class UserService : Service /// /// Gets or sets the repository. The built-in IoC used with ServiceStack autowires this property. /// - public IRepository Repository { get; set; } + public IRedisRepository Repository { get; set; } public UserResponse Post(User request) { diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Answers.cs b/src/RedisStackOverflow.ServiceModel/Answers.cs similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Answers.cs rename to src/RedisStackOverflow.ServiceModel/Answers.cs diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Properties/AssemblyInfo.cs b/src/RedisStackOverflow.ServiceModel/Properties/AssemblyInfo.cs similarity index 52% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Properties/AssemblyInfo.cs rename to src/RedisStackOverflow.ServiceModel/Properties/AssemblyInfo.cs index 519dce1..8000ebe 100644 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Properties/AssemblyInfo.cs +++ b/src/RedisStackOverflow.ServiceModel/Properties/AssemblyInfo.cs @@ -2,35 +2,18 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following +// General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("RedisStackOverflow.ServiceModel")] -[assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("RedisStackOverflow.ServiceModel")] -[assembly: AssemblyCopyright("Copyright © 2012")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("76755aba-bf3d-474d-a68a-9d9f602caa8b")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: Guid("bcd1000e-a615-49eb-b6e9-72b2a22bc93a")] diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Questions.cs b/src/RedisStackOverflow.ServiceModel/Questions.cs similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Questions.cs rename to src/RedisStackOverflow.ServiceModel/Questions.cs diff --git a/src/RedisStackOverflow.ServiceModel/RedisStackOverflow.ServiceModel.csproj b/src/RedisStackOverflow.ServiceModel/RedisStackOverflow.ServiceModel.csproj new file mode 100755 index 0000000..3ec5e10 --- /dev/null +++ b/src/RedisStackOverflow.ServiceModel/RedisStackOverflow.ServiceModel.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + RedisStackOverflow.ServiceModel + RedisStackOverflow.ServiceModel + false + false + false + + + + + + + diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Reset.cs b/src/RedisStackOverflow.ServiceModel/Reset.cs similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Reset.cs rename to src/RedisStackOverflow.ServiceModel/Reset.cs diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Stats.cs b/src/RedisStackOverflow.ServiceModel/Stats.cs similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Stats.cs rename to src/RedisStackOverflow.ServiceModel/Stats.cs diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Users.cs b/src/RedisStackOverflow.ServiceModel/Users.cs similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow.ServiceModel/Users.cs rename to src/RedisStackOverflow.ServiceModel/Users.cs diff --git a/src/RedisStackOverflow.sln b/src/RedisStackOverflow.sln new file mode 100644 index 0000000..f185c1e --- /dev/null +++ b/src/RedisStackOverflow.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26114.2 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisStackOverflow", "RedisStackOverflow\RedisStackOverflow.csproj", "{9519431C-487D-4F53-88B8-1371635A0C78}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisStackOverflow.ServiceInterface", "RedisStackOverflow.ServiceInterface\RedisStackOverflow.ServiceInterface.csproj", "{FF2B473F-40BB-42EB-966D-B32BEF951D02}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisStackOverflow.ServiceModel", "RedisStackOverflow.ServiceModel\RedisStackOverflow.ServiceModel.csproj", "{BCD1000E-A615-49EB-B6E9-72B2A22BC93A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9519431C-487D-4F53-88B8-1371635A0C78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9519431C-487D-4F53-88B8-1371635A0C78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9519431C-487D-4F53-88B8-1371635A0C78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9519431C-487D-4F53-88B8-1371635A0C78}.Release|Any CPU.Build.0 = Release|Any CPU + {FF2B473F-40BB-42EB-966D-B32BEF951D02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF2B473F-40BB-42EB-966D-B32BEF951D02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF2B473F-40BB-42EB-966D-B32BEF951D02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF2B473F-40BB-42EB-966D-B32BEF951D02}.Release|Any CPU.Build.0 = Release|Any CPU + {BCD1000E-A615-49EB-B6E9-72B2A22BC93A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCD1000E-A615-49EB-B6E9-72B2A22BC93A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCD1000E-A615-49EB-B6E9-72B2A22BC93A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCD1000E-A615-49EB-B6E9-72B2A22BC93A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/RedisStackOverflow/Configure.AppHost.cs b/src/RedisStackOverflow/Configure.AppHost.cs new file mode 100644 index 0000000..b251702 --- /dev/null +++ b/src/RedisStackOverflow/Configure.AppHost.cs @@ -0,0 +1,27 @@ +using Funq; +using ServiceStack.Redis; +using RedisStackOverflow.ServiceInterface; + +[assembly: HostingStartup(typeof(AppHost))] + +namespace RedisStackOverflow; + +public class AppHost : AppHostBase, IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => { + // Configure IOC Dependencies + services.AddSingleton(c => new RedisRepository(c.Resolve())); + }); + + public AppHost() : base("Redis Overflow", typeof(QuestionsService).Assembly) {} + + public override void Configure(Container container) + { + // Configure ServiceStack only IOC, Config & Plugins + SetConfig(new HostConfig { + DebugMode = true, + UseCamelCase = false, + }); + } +} diff --git a/src/RedisStackOverflow/Configure.Auth.cs b/src/RedisStackOverflow/Configure.Auth.cs new file mode 100644 index 0000000..0cc2c61 --- /dev/null +++ b/src/RedisStackOverflow/Configure.Auth.cs @@ -0,0 +1,30 @@ +using ServiceStack.Auth; +using ServiceStack.Redis; + +[assembly: HostingStartup(typeof(ConfigureAuth))] + +namespace RedisStackOverflow +{ + // Add any additional metadata properties you want to store in the Users Typed Session + public class CustomUserSession : AuthUserSession { } + + public class ConfigureAuth : IHostingStartup + { + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices(services => { + services.AddSingleton(c => new RedisAuthRepository(c.Resolve())); + }) + .ConfigureAppHost(appHost => { + var appSettings = appHost.AppSettings; + appHost.Plugins.Add(new AuthFeature(() => new CustomUserSession(), + new IAuthProvider[] { + new CredentialsAuthProvider(appSettings), /* Sign In with Username / Password credentials */ + })); + + var authRepo = appHost.Resolve(); + authRepo.InitSchema(); + authRepo.CreateUsers(); + }); + + } +} diff --git a/src/RedisStackOverflow/Configure.Redis.cs b/src/RedisStackOverflow/Configure.Redis.cs new file mode 100644 index 0000000..52e3721 --- /dev/null +++ b/src/RedisStackOverflow/Configure.Redis.cs @@ -0,0 +1,18 @@ +using ServiceStack.Admin; +using ServiceStack.Redis; + +[assembly: HostingStartup(typeof(ConfigureRedis))] + +namespace RedisStackOverflow; + +public class ConfigureRedis : IHostingStartup +{ + public void Configure(IWebHostBuilder builder) => builder + .ConfigureServices((context, services) => { + services.AddSingleton( + new RedisManagerPool(Environment.GetEnvironmentVariable("REDIS_HOST") ?? "localhost:6379")); + }) + .ConfigureAppHost(host => { + host.Plugins.Add(new AdminRedisFeature()); + }); +} diff --git a/src/RedisStackOverflow/Program.cs b/src/RedisStackOverflow/Program.cs new file mode 100644 index 0000000..8c51fc2 --- /dev/null +++ b/src/RedisStackOverflow/Program.cs @@ -0,0 +1,15 @@ +var builder = WebApplication.CreateBuilder(args); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + app.UseHttpsRedirection(); +} +app.UseServiceStack(new AppHost()); + +app.Run(); diff --git a/src/RedisStackOverflow/Properties/launchSettings.json b/src/RedisStackOverflow/Properties/launchSettings.json new file mode 100644 index 0000000..fcfbe3d --- /dev/null +++ b/src/RedisStackOverflow/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56629/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "RedisStackOverflow": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "https://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/Properties/AssemblyInfo.cs b/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/Properties/AssemblyInfo.cs deleted file mode 100644 index ba92871..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Questions.ServiceInterface")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ServiceStack.Questions.ServiceInterface")] -[assembly: AssemblyCopyright("Copyright © 2010")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d25a237b-f15c-475e-9666-de2ebac291a2")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] - -[assembly: ContractNamespace("http://schemas.servicestack.net/types", - ClrNamespace = "ServiceStack.MovieRest")] \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/RedisStackOverflow.ServiceInterface.csproj b/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/RedisStackOverflow.ServiceInterface.csproj deleted file mode 100644 index 677211c..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/RedisStackOverflow.ServiceInterface.csproj +++ /dev/null @@ -1,141 +0,0 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {41C8C157-B2A4-4DDE-B153-233E46109F39} - Library - Properties - RedisStackOverflow.ServiceInterface - RedisStackOverflow.ServiceInterface - v4.5 - 512 - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - AllRules.ruleset - false - - - - ..\packages\ServiceStack.4.5.0\lib\net45\ServiceStack.dll - True - - - ..\packages\ServiceStack.Client.4.5.0\lib\net45\ServiceStack.Client.dll - True - - - ..\packages\ServiceStack.Common.4.5.0\lib\net45\ServiceStack.Common.dll - True - - - ..\packages\ServiceStack.Interfaces.4.5.0\lib\portable-wp80+sl5+net45+win8+wpa81+monotouch+monoandroid+xamarin.ios10\ServiceStack.Interfaces.dll - True - - - ..\packages\ServiceStack.Redis.4.5.0\lib\net45\ServiceStack.Redis.dll - True - - - ..\packages\ServiceStack.Text.4.5.0\lib\net45\ServiceStack.Text.dll - True - - - - 3.5 - - - 3.0 - - - 3.5 - - - 3.5 - - - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - {BFAB0E32-43F3-4A0E-9409-FC57FCF4BF14} - RedisStackOverflow.ServiceModel - - - - - - - - \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/packages.config b/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/packages.config deleted file mode 100644 index 1b4c760..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/RedisStackOverflow.ServiceModel.csproj b/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/RedisStackOverflow.ServiceModel.csproj deleted file mode 100644 index 563eeff..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/RedisStackOverflow.ServiceModel.csproj +++ /dev/null @@ -1,67 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {BFAB0E32-43F3-4A0E-9409-FC57FCF4BF14} - Library - Properties - RedisStackOverflow.ServiceModel - RedisStackOverflow.ServiceModel - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\packages\ServiceStack.Interfaces.4.5.0\lib\portable-wp80+sl5+net45+win8+wpa81+monotouch+monoandroid+xamarin.ios10\ServiceStack.Interfaces.dll - True - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/packages.config b/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/packages.config deleted file mode 100644 index 5c87f86..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow.ServiceModel/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow.csproj b/src/RedisStackOverflow/RedisStackOverflow.csproj new file mode 100755 index 0000000..5ca1b7a --- /dev/null +++ b/src/RedisStackOverflow/RedisStackOverflow.csproj @@ -0,0 +1,27 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow.sln b/src/RedisStackOverflow/RedisStackOverflow.sln deleted file mode 100644 index 22b930e..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow.sln +++ /dev/null @@ -1,32 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisStackOverflow.ServiceInterface", "RedisStackOverflow.ServiceInterface\RedisStackOverflow.ServiceInterface.csproj", "{41C8C157-B2A4-4DDE-B153-233E46109F39}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisStackOverflow", "RedisStackOverflow\RedisStackOverflow.csproj", "{CCAC74EA-08EE-41A8-BF01-8F1B7D207144}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedisStackOverflow.ServiceModel", "RedisStackOverflow.ServiceModel\RedisStackOverflow.ServiceModel.csproj", "{BFAB0E32-43F3-4A0E-9409-FC57FCF4BF14}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {41C8C157-B2A4-4DDE-B153-233E46109F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41C8C157-B2A4-4DDE-B153-233E46109F39}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41C8C157-B2A4-4DDE-B153-233E46109F39}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41C8C157-B2A4-4DDE-B153-233E46109F39}.Release|Any CPU.Build.0 = Release|Any CPU - {CCAC74EA-08EE-41A8-BF01-8F1B7D207144}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CCAC74EA-08EE-41A8-BF01-8F1B7D207144}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CCAC74EA-08EE-41A8-BF01-8F1B7D207144}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CCAC74EA-08EE-41A8-BF01-8F1B7D207144}.Release|Any CPU.Build.0 = Release|Any CPU - {BFAB0E32-43F3-4A0E-9409-FC57FCF4BF14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BFAB0E32-43F3-4A0E-9409-FC57FCF4BF14}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BFAB0E32-43F3-4A0E-9409-FC57FCF4BF14}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BFAB0E32-43F3-4A0E-9409-FC57FCF4BF14}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/btn-github.png b/src/RedisStackOverflow/RedisStackOverflow/Content/Images/btn-github.png deleted file mode 100644 index 3e1e869..0000000 Binary files a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/btn-github.png and /dev/null differ diff --git a/src/RedisStackOverflow/RedisStackOverflow/Global.asax b/src/RedisStackOverflow/RedisStackOverflow/Global.asax deleted file mode 100644 index 692f6ca..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="RedisStackOverflow.Global" Language="C#" %> diff --git a/src/RedisStackOverflow/RedisStackOverflow/Global.asax.cs b/src/RedisStackOverflow/RedisStackOverflow/Global.asax.cs deleted file mode 100644 index e42da2b..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow/Global.asax.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using Funq; -using RedisStackOverflow.ServiceInterface; -using ServiceStack; -using ServiceStack.Configuration; -using ServiceStack.Logging; -using ServiceStack.Redis; -using IRepository = RedisStackOverflow.ServiceInterface.IRepository; - -namespace RedisStackOverflow -{ - /// - /// Create your ServiceStack web service application with a singleton AppHost. - /// - public class AppHost : AppHostBase - { - /// - /// Initializes a new instance of your ServiceStack application, with the specified name and assembly containing the services. - /// - public AppHost() : base("Redis StackOverflow", typeof(QuestionsService).Assembly) { } - - /// - /// Configure the container with the necessary routes for your ServiceStack application. - /// - /// The built-in IoC used with ServiceStack. - public override void Configure(Container container) - { - //Show StackTrace in Web Service Exceptions - SetConfig(new HostConfig { DebugMode = true }); - - //Register any dependencies you want injected into your services - container.Register(c => new PooledRedisClientManager()); - container.Register(c => new Repository(c.Resolve())); - } - } - - public class Global : System.Web.HttpApplication - { - protected void Application_Start(object sender, EventArgs e) - { - if (ConfigUtils.GetAppSetting("log", false)) { LogManager.LogFactory = new ConsoleLogFactory(); } - - //Initialize your application - (new AppHost()).Init(); - } - } -} \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow/Properties/AssemblyInfo.cs b/src/RedisStackOverflow/RedisStackOverflow/Properties/AssemblyInfo.cs deleted file mode 100644 index a98296d..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Questions.Host.Web")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ServiceStack.Questions.Host.Web")] -[assembly: AssemblyCopyright("Copyright © 2010")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("3d5900ae-111a-45be-96b3-d9e4606ca793")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/RedisStackOverflow/RedisStackOverflow/Properties/PublishProfiles/WebDeploy.pubxml b/src/RedisStackOverflow/RedisStackOverflow/Properties/PublishProfiles/WebDeploy.pubxml deleted file mode 100644 index 2370eb8..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow/Properties/PublishProfiles/WebDeploy.pubxml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - MSDeploy - Release - Any CPU - http://redisstackoverflow.servicestack.net - True - False - awstest.servicestack.net - RedisStackOverflow - - True - WMSVC - True - deploy - <_SavePWD>True - False - - \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow/RedisStackOverflow.csproj b/src/RedisStackOverflow/RedisStackOverflow/RedisStackOverflow.csproj deleted file mode 100644 index de0b44d..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow/RedisStackOverflow.csproj +++ /dev/null @@ -1,163 +0,0 @@ - - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {CCAC74EA-08EE-41A8-BF01-8F1B7D207144} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - RedisStackOverflow - RedisStackOverflow - v4.5 - - - 4.0 - - - false - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - false - - - pdbonly - true - bin\ - TRACE - prompt - 4 - AllRules.ruleset - false - - - - ..\packages\ServiceStack.4.5.0\lib\net45\ServiceStack.dll - True - - - ..\packages\ServiceStack.Client.4.5.0\lib\net45\ServiceStack.Client.dll - True - - - ..\packages\ServiceStack.Common.4.5.0\lib\net45\ServiceStack.Common.dll - True - - - ..\packages\ServiceStack.Interfaces.4.5.0\lib\portable-wp80+sl5+net45+win8+wpa81+monotouch+monoandroid+xamarin.ios10\ServiceStack.Interfaces.dll - True - - - ..\packages\ServiceStack.Redis.4.5.0\lib\net45\ServiceStack.Redis.dll - True - - - ..\packages\ServiceStack.Text.4.5.0\lib\net45\ServiceStack.Text.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - Global.asax - - - - - - {41C8C157-B2A4-4DDE-B153-233E46109F39} - RedisStackOverflow.ServiceInterface - - - {BFAB0E32-43F3-4A0E-9409-FC57FCF4BF14} - RedisStackOverflow.ServiceModel - - - - - - - - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - - False - True - 53208 - / - http://localhost/RedisStackOverflow - False - False - - - False - - - - - \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow/Web.config b/src/RedisStackOverflow/RedisStackOverflow/Web.config deleted file mode 100644 index e223ee8..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow/Web.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow/packages.config b/src/RedisStackOverflow/RedisStackOverflow/packages.config deleted file mode 100644 index 1b4c760..0000000 --- a/src/RedisStackOverflow/RedisStackOverflow/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Css/default.css b/src/RedisStackOverflow/wwwroot/Content/Css/default.css similarity index 99% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Css/default.css rename to src/RedisStackOverflow/wwwroot/Content/Css/default.css index 2534667..41fca7a 100644 --- a/src/RedisStackOverflow/RedisStackOverflow/Content/Css/default.css +++ b/src/RedisStackOverflow/wwwroot/Content/Css/default.css @@ -427,7 +427,7 @@ H4 EM font-size:11px; } -#mono +#netcore { display: block; float: right; diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/Mono-powered-big.png b/src/RedisStackOverflow/wwwroot/Content/Images/Mono-powered-big.png similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Images/Mono-powered-big.png rename to src/RedisStackOverflow/wwwroot/Content/Images/Mono-powered-big.png diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/arrow-down.png b/src/RedisStackOverflow/wwwroot/Content/Images/arrow-down.png similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Images/arrow-down.png rename to src/RedisStackOverflow/wwwroot/Content/Images/arrow-down.png diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/arrow-left.png b/src/RedisStackOverflow/wwwroot/Content/Images/arrow-left.png similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Images/arrow-left.png rename to src/RedisStackOverflow/wwwroot/Content/Images/arrow-left.png diff --git a/src/RedisStackOverflow/wwwroot/Content/Images/aspnetcore.png b/src/RedisStackOverflow/wwwroot/Content/Images/aspnetcore.png new file mode 100644 index 0000000..3d9fb93 Binary files /dev/null and b/src/RedisStackOverflow/wwwroot/Content/Images/aspnetcore.png differ diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/bg-body-1024.png b/src/RedisStackOverflow/wwwroot/Content/Images/bg-body-1024.png similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Images/bg-body-1024.png rename to src/RedisStackOverflow/wwwroot/Content/Images/bg-body-1024.png diff --git a/src/RedisStackOverflow/wwwroot/Content/Images/btn-github.png b/src/RedisStackOverflow/wwwroot/Content/Images/btn-github.png new file mode 100644 index 0000000..e7605b0 Binary files /dev/null and b/src/RedisStackOverflow/wwwroot/Content/Images/btn-github.png differ diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/delete_icon.png b/src/RedisStackOverflow/wwwroot/Content/Images/delete_icon.png similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Images/delete_icon.png rename to src/RedisStackOverflow/wwwroot/Content/Images/delete_icon.png diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/myvote-down.png b/src/RedisStackOverflow/wwwroot/Content/Images/myvote-down.png similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Images/myvote-down.png rename to src/RedisStackOverflow/wwwroot/Content/Images/myvote-down.png diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/myvote-up.png b/src/RedisStackOverflow/wwwroot/Content/Images/myvote-up.png similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Images/myvote-up.png rename to src/RedisStackOverflow/wwwroot/Content/Images/myvote-up.png diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/vote-down.png b/src/RedisStackOverflow/wwwroot/Content/Images/vote-down.png similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Images/vote-down.png rename to src/RedisStackOverflow/wwwroot/Content/Images/vote-down.png diff --git a/src/RedisStackOverflow/RedisStackOverflow/Content/Images/vote-up.png b/src/RedisStackOverflow/wwwroot/Content/Images/vote-up.png similarity index 100% rename from src/RedisStackOverflow/RedisStackOverflow/Content/Images/vote-up.png rename to src/RedisStackOverflow/wwwroot/Content/Images/vote-up.png diff --git a/src/RedisStackOverflow/RedisStackOverflow/default.htm b/src/RedisStackOverflow/wwwroot/default.htm similarity index 89% rename from src/RedisStackOverflow/RedisStackOverflow/default.htm rename to src/RedisStackOverflow/wwwroot/default.htm index 545827e..03cefac 100644 --- a/src/RedisStackOverflow/RedisStackOverflow/default.htm +++ b/src/RedisStackOverflow/wwwroot/default.htm @@ -4,41 +4,34 @@ ServiceStack Redis StackOverflow - +