include webpack, build configuration for typescript

This commit is contained in:
2023-12-05 12:55:59 -06:00
parent 6476972508
commit dc8ed06663
17 changed files with 175 additions and 99 deletions

View File

@@ -0,0 +1,2 @@
npm run build
docker compose build

View File

@@ -0,0 +1,2 @@
npm run build
docker compose build

View File

@@ -2,6 +2,9 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
# install nodejs for typescript support
RUN apt-get update && apt-get -y install nodejs
ENV CLIENT_PORT=${CLIENT_PORT:-8080}
ENV AWS_S3_URL=$AWS_S3_URL
ENV AWS_ACCESS_KEY=$AWS_ACCESS_KEY

View File

@@ -59,13 +59,6 @@ builder.Services.AddServerSideBlazor();
// build app
var app = builder.Build();
// apply most recent migration to db
using (IServiceScope scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<UnbinderDbContext>();
db.Database.Migrate();
}
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
@@ -83,4 +76,14 @@ app.MapDefaultControllerRoute();
Initializer.Seed(app);
// apply most recent migration to db
using (IServiceScope scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<UnbinderDbContext>();
if (db.Database.GetPendingMigrations().Any())
{
db.Database.Migrate();
}
}
app.Run();

View File

@@ -21,10 +21,6 @@
<PackageReference Include="Microsoft.Identity.Web" Version="2.15.3" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="2.15.3" />
<PackageReference Include="Microsoft.SqlServer.Server" Version="1.0.0" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.3.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -36,4 +36,6 @@
</div>
</form>
<script src="~/js/createRecipe.js"></script>
@section Scripts {
<script src="~/js/createRecipe.js" type="module" asp-append-version="true"></script>
}

View File

@@ -19,4 +19,7 @@
}
</div>
</div>
<script href="~/js/searchRecipes.js"></script>
@section Scripts {
<script src="~/js/searchRecipes.js" type="module" asp-append-version="true"></script>
}

View File

@@ -2,10 +2,7 @@
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<script type="text/javascript">var exports = {};</script>
<script src="~/js/site.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="~/lib/pdf.js/pdf.min.mjs"></script>
<link href="~/css/site.css" rel="stylesheet" />
<base href="/" />
</head>
@@ -16,4 +13,6 @@
</main>
<partial name="_Footer" />
</body>
@RenderSection("Scripts", required: true)
</html>

View File

@@ -4,18 +4,15 @@
"description": "",
"main": "wwwroot/js/index.js",
"scripts": {
"build": "tsc",
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Mikayla Dobson",
"license": "ISC",
"devDependencies": {
"eslint": "^8.55.0",
"eslint-config-semistandard": "^17.0.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-promise": "^6.1.1",
"typescript": "^5.3.2"
"ts-loader": "^9.5.1",
"typescript": "^5.3.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
}
}

View File

@@ -1,14 +1,13 @@
{
"compileOnSave": true,
"compilerOptions": {
"noImplicitAny": false,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"lib": ["ES2015", "DOM"],
"module": "CommonJS",
"esModuleInterop": true,
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ES5",
"moduleResolution": "Node",
"outDir": "wwwroot/js"
},
"include": [

View File

@@ -0,0 +1,38 @@
const path = require('path');
const fs = require('fs');
const jsRoot = path.resolve(__dirname, 'wwwroot/js');
if (fs.existsSync(jsRoot)) {
fs.rmdirSync(jsRoot, { recursive: true });
}
module.exports = {
entry: {
searchRecipes: "./wwwroot/ts/searchRecipes.ts",
createRecipe: "./wwwroot/ts/createRecipe.ts",
"types/validators": "./wwwroot/ts/types/validators.ts",
"types/models": "./wwwroot/ts/types/models.ts",
types: "./wwwroot/ts/types/index.ts",
},
mode: 'production',
optimization: {
minimize: false,
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
]
},
resolve: {
extensions: ['.ts', '.js'],
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'wwwroot/js'),
}
}

View File

@@ -1,4 +1,6 @@
async function getAllIngredients () {
console.log("imported createRecipe");
async function getAllIngredients() {
const ingredients = await fetch('/api/ingredients');
return await ingredients.json();
}

View File

@@ -1,52 +1,70 @@
import { isRecipeArray, type Recipe } from "./types";
import { isRecipeArray } from "./types/validators";
import type { Recipe } from "./types/models";
function setSearchResultMessage(model: Recipe[]): void {
document.addEventListener('DOMContentLoaded', () => {
console.log("Loaded searchRecipes.ts");
// dom elements
const searchButton = document.getElementById("execute-search");
const message = document.getElementById("query-message");
message.innerHTML = `Viewing ${model.length} results for: ${window.location.search.split("=")[1]}`;
let textField: HTMLInputElement | null = null;
const inputNodes = document.querySelectorAll("input");
// functions
function setSearchResultMessage(model: Recipe[], query?: string): void {
message.innerHTML = `Viewing ${model.length} results for: ${query ?? window.location.search.split("=")[1]}`;
}
async function handleSearch(textField?: HTMLInputElement) {
function checkForTextField() {
inputNodes.forEach(node => {
if (node.id == "new-search-query") {
console.log('found text field');
textField = node;
}
})
}
async function handleSearch() {
console.log("__SEARCH HANDLER__");
if (!textField) {
console.log("NO TEXT FIELD!");
checkForTextField();
if (!textField) {
console.log("text field was not found :(");
return;
}
}
console.log("found text field, searching...");
const newResult = await fetch(`/api/recipe/search?q=${textField.value}`);
console.log({ newResult });
const newResultJson = await newResult.json();
console.log({ newResultJson });
const searchResultsContainer = document.getElementById("search-results");
if (isRecipeArray(newResultJson)) {
const isRecipeArr = isRecipeArray(newResultJson);
console.log({ isRecipeArr });
if (isRecipeArr) {
let updatedHTML = "";
for (const recipe of newResultJson) {
updatedHTML += `
<p>${recipe.Name}</p>
<p>${recipe.ShortDescription}</p>
<p>${recipe.name}</p>
<p>${recipe.shortDescription}</p>
`;
}
console.log(updatedHTML);
console.log({ updatedHTML });
setSearchResultMessage(newResultJson);
setSearchResultMessage(newResultJson, textField.value);
searchResultsContainer.innerHTML = updatedHTML;
}
}
document.addEventListener('DOMContentLoaded', () => {
console.log("Loaded DOM");
// dom elements
const searchButton = document.getElementById("execute-search");
const inputNodes = document.querySelectorAll("input");
let textField: HTMLInputElement | null = null;
inputNodes.forEach(node => {
if (node.id == "new-search-query") {
textField = node;
console.log("Found text field");
}
});
// handle search updates on client side
searchButton.onclick = async () => handleSearch(textField);
// perform actions and attach event listeners
checkForTextField();
searchButton.onclick = async () => handleSearch();
});

View File

@@ -1,2 +1,14 @@
export * from "./models";
export * from "./validators";
export {
isRecipe,
isArrayOfType,
isIngredient,
isIngredientArray,
isRecipeArray
} from "./validators";
export type {
Recipe,
Ingredient,
RecipeIngredient,
IngredientWithDetails,
} from "./models";

View File

@@ -1,25 +1,25 @@
export type Recipe = {
RecipeId: number,
Name: string,
ShortDescription: string,
S3Url?: string,
Author?: string,
RecipeText?: string,
MainImageUrl?: string,
recipeId: number,
name: string,
shortDescription: string,
s3Url?: string,
author?: string,
recipeText?: string,
mainImageUrl?: string,
}
export type Ingredient = {
IngredientId: number,
Name: string,
Description?: string
ingredientId: number,
name: string,
description?: string
}
export type RecipeIngredient = {
RecipeIngredientId: number,
RecipeId: number,
IngredientId: number,
Amount?: number,
Unit?: string,
recipeIngredientId: number,
recipeId: number,
ingredientId: number,
amount?: number,
unit?: string,
}
export type IngredientWithDetails = Ingredient & Pick<RecipeIngredient, 'Amount' | 'Unit'>
export type IngredientWithDetails = Ingredient & Pick<RecipeIngredient, 'amount' | 'unit'>

View File

@@ -4,12 +4,12 @@ export function isRecipe(input: unknown): input is Recipe {
return (
typeof input == 'object'
&& input != null
&& 'RecipeId' in input
&& 'Name' in input
&& 'ShortDescription' in input
&& (input as Recipe).RecipeId != undefined
&& (input as Recipe).Name != undefined
&& (input as Recipe).ShortDescription != undefined
&& 'recipeId' in input
&& 'name' in input
&& 'shortDescription' in input
&& (input as Recipe).recipeId != undefined
&& (input as Recipe).name != undefined
&& (input as Recipe).shortDescription != undefined
);
}
@@ -25,10 +25,10 @@ export function isIngredient(input: unknown): input is Ingredient {
return (
typeof input == 'object'
&& input != null
&& 'IngredientId' in input
&& 'Name' in input
&& (input as Ingredient).IngredientId != undefined
&& (input as Ingredient).Name != undefined
&& 'ingredientId' in input
&& 'name' in input
&& (input as Ingredient).ingredientId != undefined
&& (input as Ingredient).name != undefined
);
}