include webpack, build configuration for typescript
This commit is contained in:
2
Unbinder/BuildTools/build.ps1
Normal file
2
Unbinder/BuildTools/build.ps1
Normal file
@@ -0,0 +1,2 @@
|
||||
npm run build
|
||||
docker compose build
|
||||
2
Unbinder/BuildTools/build.sh
Normal file
2
Unbinder/BuildTools/build.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
npm run build
|
||||
docker compose build
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": [
|
||||
|
||||
38
Unbinder/webpack.config.js
Normal file
38
Unbinder/webpack.config.js
Normal 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'),
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
async function getAllIngredients () {
|
||||
console.log("imported createRecipe");
|
||||
|
||||
async function getAllIngredients() {
|
||||
const ingredients = await fetch('/api/ingredients');
|
||||
return await ingredients.json();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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";
|
||||
@@ -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'>
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user