13 changed files with 3984 additions and 6263 deletions
@@ -8,10 +8,10 @@ on:
types: [closed] types: [closed]
branches: branches:
- production - production
workflow_dispatch:
jobs: jobs:
build_and_deploy_job: build_and_deploy_job:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'workflow_dispatch' if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Build and Deploy Job name: Build and Deploy Job
steps: steps:
@@ -43,4 +43,3 @@ jobs:
with: with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ZEALOUS_CLIFF_09A0A7B10 }} azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ZEALOUS_CLIFF_09A0A7B10 }}
action: "close" action: "close"
app_location: "/client"
@@ -1,55 +0,0 @@
name: Build and deploy ASP.Net Core app to Azure Web App - criminalmindsapi
env:
WORKING_DIRECTORY: ./server
on:
push:
branches:
- production
workflow_dispatch:
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Build with dotnet
run: dotnet build --configuration Release
working-directory: ${{ env.WORKING_DIRECTORY }}
- name: dotnet publish
run: dotnet publish -c Release -o release
working-directory: ${{ env.WORKING_DIRECTORY }}
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: server
path: ./server/release
deploy:
runs-on: windows-latest
needs: build
environment:
name: 'production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
permissions:
id-token: write
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4.1.7
with:
name: server
- name: Login to Azure
uses: azure/login@v1
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_936FD4F727B94B4CA24C582D0863DF06 }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_0FB05F3F513C480395D15114ABAF36E0 }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_D1C0B88C12EF49D0BEEB5F2ACE6EC4FC }}
- name: Deploy to Azure Web App
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: 'criminalmindsapi'
slot-name: 'production'
package: .
+3801 -6161
View File
File diff suppressed because it is too large Load Diff
+4 -8
View File
@@ -12,9 +12,7 @@
"@testing-library/react": "^13.3.0", "@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"http-proxy-middleware": "^2.0.7", "http-proxy-middleware": "^2.0.6",
"loader-utils": ">=2.0.3",
"minimatch": ">=3.0.5",
"nth-check": ">=2.0.1", "nth-check": ">=2.0.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.4.0", "react-bootstrap": "^2.4.0",
@@ -23,9 +21,9 @@
"react-loading-icons": "^1.1.0", "react-loading-icons": "^1.1.0",
"react-router": "^6.3.0", "react-router": "^6.3.0",
"react-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"react-scripts": "^5.0.1", "react-scripts": "5.0.1",
"terser": ">=5.14.2", "web-vitals": "^2.1.4",
"web-vitals": "^2.1.4" "terser": ">=5.14.2"
}, },
"scripts": { "scripts": {
"start": "set HTTPS=true&&react-scripts start", "start": "set HTTPS=true&&react-scripts start",
@@ -56,8 +54,6 @@
"devDependencies": { "devDependencies": {
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"jest-editor-support": "^30.1.0", "jest-editor-support": "^30.1.0",
"loader-utils": ">=2.0.3",
"minimatch": ">=3.0.5",
"terser": ">=5.14.2" "terser": ">=5.14.2"
} }
} }
+1 -1
View File
@@ -3,7 +3,7 @@ import Card from 'react-bootstrap/Card';
import Table from 'react-bootstrap/Table'; import Table from 'react-bootstrap/Table';
export default function CharacterCard(props) { export default function CharacterCard(props) {
const image = new URL(props.character.image).pathname; const image = new URL(props.character.image).pathname;
const characterName = props.character.fullName; const characterName = props.character.fullName;
const firstEpisode = props.character.firstEpisode; const firstEpisode = props.character.firstEpisode;
+16
View File
@@ -0,0 +1,16 @@
import React from 'react';
export default function DataButton(props) {
const label = props.label;
return (
<button
value={label.toLowerCase()}
onClick={(e) => props.handleClick(e.target.value)}
>
{props.label}
</button>
);
}
+17
View File
@@ -0,0 +1,17 @@
import React from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
export default function DataButtonHolder(props) {
return(
<Container fluid>
<Row className="d-flex flex-row justify-content-center align-items-center">
{props.children}
</Row>
</Container>
);
}
+21
View File
@@ -0,0 +1,21 @@
import React from 'react';
import Card from 'react-bootstrap/Card';
export default function QuoteCard(props) {
const text = props.quote.text;
const source = props.quote.source
return (
<Card style={{ width: '22rem' }} className='p-1 m-3'>
<Card.Body>
<Card.Text>{text}</Card.Text>
<Card.Text>{source}</Card.Text>
</Card.Body>
</Card>
);
}
+89 -20
View File
@@ -5,44 +5,111 @@ import CardHolder from '../components/cardHolder';
import CharacterCard from '../components/characterCard'; import CharacterCard from '../components/characterCard';
import Loading from '../components/loading'; import Loading from '../components/loading';
import LoadingError from '../components/loadingError'; import LoadingError from '../components/loadingError';
import DataButtonHolder from '../components/dataButtonHolder';
import DataButton from '../components/dataButton';
import CharacterService from '../services/characterService'; import CharacterService from '../services/characterService';
import QuoteService from '../services/quoteService';
import QuoteCard from '../components/quoteCard';
const characterService = new CharacterService(); const characterService = new CharacterService();
const quoteService = new QuoteService();
export default function Home() { export default function Home() {
const [characters, setCharacters] = useState([]); const [currentData, setCurrentData] = useState('characters');
const [data, setData] = useState([]);
const [errorMessage, setErrorMessage] = useState(null); const [errorMessage, setErrorMessage] = useState(null);
const getCharacters = async () => { const getCharacters = async () => {
setData([]);
setCurrentData('characters');
const res = await characterService.getCharacters(); const res = await characterService.getCharacters();
if (!res.ok) { if (!res.ok) {
return setErrorMessage('Failed to load characters.'); return setErrorMessage(`Failed to load characters`);
} }
const characters = await res.json(); const characters = await res.json();
return setCharacters(characters); return setData(characters);
} }
const getQuotes = async () => {
setData([]);
setCurrentData('quotes');
const res = await quoteService.getQuotes();
if (!res.ok) {
return setErrorMessage(`Failed to load quotes`);
}
const quotes = await res.json();
return setData(quotes.slice(0,20));
}
const getEpisodes = () => {console.log('episodes')}
const getSeasons = () => {console.log('seasons')}
useEffect(() => { useEffect(() => {
getCharacters(); getCharacters();
}, []); }, []);
const getCharacterCards = () => { const models = [{ id: 1, name: 'Characters', handleClick: getCharacters},
{ id: 2, name: 'Quotes', handleClick: getQuotes },
{ id: 3, name: 'Episodes', handleClick: getEpisodes },
{ id: 4, name: 'Seasons', handleClick: getSeasons }];
return characters.map(character => { const getDataButtons = () => {
return models.map(model => {
return (
<DataButton
label={model.name}
handleClick={model.handleClick}
key={model.id}
/>
);
});
}
const getCards = () => {
if (currentData === 'quotes') {
return data.map(quote => {
return (
<QuoteCard
quote={quote}
key={quote.id}
/>
);
});
}
return data.map(character => {
return ( return (
<CharacterCard <CharacterCard
character={character} character={character}
key={character.id} key={character.id}
/> />
@@ -57,30 +124,32 @@ export default function Home() {
setErrorMessage(null); setErrorMessage(null);
if (errorMessage === 'Failed to load characters.') return await getCharacters();
} }
return ( return (
<> <>
<Greeting /> <Greeting />
{characters.length > 0 ? <DataButtonHolder>
{getDataButtons()}
</DataButtonHolder>
{data.length > 0 ?
<CardHolder> <CardHolder>
{getCharacterCards()} {getCards()}
</CardHolder> </CardHolder>
: errorMessage ?
<LoadingError : errorMessage ?
message={errorMessage}
handleClick={handleTryAgain} <LoadingError
/> message={errorMessage}
handleClick={handleTryAgain}
: <Loading />} />
: <Loading />}
</> </>
); );
} }
+19
View File
@@ -0,0 +1,19 @@
import { baseUrl, versionHeader, versionHeaderValue } from '../constants';
class QuoteService {
getQuotes = async () => {
const res = await fetch(`${baseUrl}/api/quotes`, {
headers: {
[versionHeader]: versionHeaderValue
}
});
return res;
}
}
export default QuoteService;
+7 -8
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
@@ -9,16 +9,15 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.1" /> <PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.8" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="moq" Version="4.20.72" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit" Version="2.9.2" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2"> <PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
@@ -92,7 +92,6 @@ namespace server.tests.v1.UnitTests
character.Should().BeOfType<Quote>(); character.Should().BeOfType<Quote>();
} }
[Fact]
public async Task GetQuoteByIdAsync_ValidIdForNonExistentQuote_Returns404StatusCodeWithProblemDetails() public async Task GetQuoteByIdAsync_ValidIdForNonExistentQuote_Returns404StatusCodeWithProblemDetails()
{ {
var quoteId = "62b7d5506c1b407771829926"; var quoteId = "62b7d5506c1b407771829926";
+7 -6
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>True</GenerateDocumentationFile> <GenerateDocumentationFile>True</GenerateDocumentationFile>
@@ -12,11 +12,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" /> <PackageReference Include="AspNetCoreRateLimit" Version="4.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.29.0" /> <PackageReference Include="MongoDB.Driver" Version="2.16.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1" /> <PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>