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
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# install nodejs for typescript support
|
||||||
|
RUN apt-get update && apt-get -y install nodejs
|
||||||
|
|
||||||
ENV CLIENT_PORT=${CLIENT_PORT:-8080}
|
ENV CLIENT_PORT=${CLIENT_PORT:-8080}
|
||||||
ENV AWS_S3_URL=$AWS_S3_URL
|
ENV AWS_S3_URL=$AWS_S3_URL
|
||||||
ENV AWS_ACCESS_KEY=$AWS_ACCESS_KEY
|
ENV AWS_ACCESS_KEY=$AWS_ACCESS_KEY
|
||||||
|
|||||||
@@ -59,13 +59,6 @@ builder.Services.AddServerSideBlazor();
|
|||||||
// build app
|
// build app
|
||||||
var app = builder.Build();
|
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.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
@@ -83,4 +76,14 @@ app.MapDefaultControllerRoute();
|
|||||||
|
|
||||||
Initializer.Seed(app);
|
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();
|
app.Run();
|
||||||
|
|||||||
@@ -21,10 +21,6 @@
|
|||||||
<PackageReference Include="Microsoft.Identity.Web" Version="2.15.3" />
|
<PackageReference Include="Microsoft.Identity.Web" Version="2.15.3" />
|
||||||
<PackageReference Include="Microsoft.Identity.Web.UI" 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.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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -36,4 +36,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</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>
|
||||||
</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>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<title>@ViewBag.Title</title>
|
<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="https://cdn.tailwindcss.com"></script>
|
||||||
<script src="~/lib/pdf.js/pdf.min.mjs"></script>
|
|
||||||
<link href="~/css/site.css" rel="stylesheet" />
|
<link href="~/css/site.css" rel="stylesheet" />
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
</head>
|
</head>
|
||||||
@@ -16,4 +13,6 @@
|
|||||||
</main>
|
</main>
|
||||||
<partial name="_Footer" />
|
<partial name="_Footer" />
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
@RenderSection("Scripts", required: true)
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -4,18 +4,15 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "wwwroot/js/index.js",
|
"main": "wwwroot/js/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "webpack",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "Mikayla Dobson",
|
"author": "Mikayla Dobson",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.55.0",
|
"ts-loader": "^9.5.1",
|
||||||
"eslint-config-semistandard": "^17.0.0",
|
"typescript": "^5.3.2",
|
||||||
"eslint-config-standard": "^17.1.0",
|
"webpack": "^5.89.0",
|
||||||
"eslint-plugin-import": "^2.29.0",
|
"webpack-cli": "^5.1.4"
|
||||||
"eslint-plugin-n": "^15.7.0",
|
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
|
||||||
"typescript": "^5.3.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
{
|
{
|
||||||
"compileOnSave": true,
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"noEmitOnError": true,
|
"noEmitOnError": true,
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"lib": ["ES2015", "DOM"],
|
"esModuleInterop": true,
|
||||||
"module": "CommonJS",
|
"moduleResolution": "Bundler",
|
||||||
|
"module": "ESNext",
|
||||||
"target": "ES5",
|
"target": "ES5",
|
||||||
"moduleResolution": "Node",
|
|
||||||
"outDir": "wwwroot/js"
|
"outDir": "wwwroot/js"
|
||||||
},
|
},
|
||||||
"include": [
|
"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');
|
const ingredients = await fetch('/api/ingredients');
|
||||||
return await ingredients.json();
|
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 {
|
|
||||||
const message = document.getElementById("query-message");
|
|
||||||
message.innerHTML = `Viewing ${model.length} results for: ${window.location.search.split("=")[1]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSearch(textField?: HTMLInputElement) {
|
|
||||||
if (!textField) {
|
|
||||||
console.log("NO TEXT FIELD!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newResult = await fetch(`/api/recipe/search?q=${textField.value}`);
|
|
||||||
const newResultJson = await newResult.json();
|
|
||||||
const searchResultsContainer = document.getElementById("search-results");
|
|
||||||
|
|
||||||
if (isRecipeArray(newResultJson)) {
|
|
||||||
let updatedHTML = "";
|
|
||||||
for (const recipe of newResultJson) {
|
|
||||||
updatedHTML += `
|
|
||||||
<p>${recipe.Name}</p>
|
|
||||||
<p>${recipe.ShortDescription}</p>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(updatedHTML);
|
|
||||||
|
|
||||||
setSearchResultMessage(newResultJson);
|
|
||||||
searchResultsContainer.innerHTML = updatedHTML;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log("Loaded DOM");
|
console.log("Loaded searchRecipes.ts");
|
||||||
|
|
||||||
// dom elements
|
// dom elements
|
||||||
const searchButton = document.getElementById("execute-search");
|
const searchButton = document.getElementById("execute-search");
|
||||||
|
const message = document.getElementById("query-message");
|
||||||
const inputNodes = document.querySelectorAll("input");
|
|
||||||
let textField: HTMLInputElement | null = null;
|
let textField: HTMLInputElement | null = null;
|
||||||
|
const inputNodes = document.querySelectorAll("input");
|
||||||
|
|
||||||
inputNodes.forEach(node => {
|
// functions
|
||||||
if (node.id == "new-search-query") {
|
function setSearchResultMessage(model: Recipe[], query?: string): void {
|
||||||
textField = node;
|
message.innerHTML = `Viewing ${model.length} results for: ${query ?? window.location.search.split("=")[1]}`;
|
||||||
console.log("Found text field");
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
checkForTextField();
|
||||||
|
if (!textField) {
|
||||||
|
console.log("text field was not found :(");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// handle search updates on client side
|
console.log("found text field, searching...");
|
||||||
searchButton.onclick = async () => handleSearch(textField);
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log({ updatedHTML });
|
||||||
|
|
||||||
|
setSearchResultMessage(newResultJson, textField.value);
|
||||||
|
searchResultsContainer.innerHTML = updatedHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform actions and attach event listeners
|
||||||
|
checkForTextField();
|
||||||
|
searchButton.onclick = async () => handleSearch();
|
||||||
});
|
});
|
||||||
@@ -1,2 +1,14 @@
|
|||||||
export * from "./models";
|
export {
|
||||||
export * from "./validators";
|
isRecipe,
|
||||||
|
isArrayOfType,
|
||||||
|
isIngredient,
|
||||||
|
isIngredientArray,
|
||||||
|
isRecipeArray
|
||||||
|
} from "./validators";
|
||||||
|
|
||||||
|
export type {
|
||||||
|
Recipe,
|
||||||
|
Ingredient,
|
||||||
|
RecipeIngredient,
|
||||||
|
IngredientWithDetails,
|
||||||
|
} from "./models";
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
export type Recipe = {
|
export type Recipe = {
|
||||||
RecipeId: number,
|
recipeId: number,
|
||||||
Name: string,
|
name: string,
|
||||||
ShortDescription: string,
|
shortDescription: string,
|
||||||
S3Url?: string,
|
s3Url?: string,
|
||||||
Author?: string,
|
author?: string,
|
||||||
RecipeText?: string,
|
recipeText?: string,
|
||||||
MainImageUrl?: string,
|
mainImageUrl?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Ingredient = {
|
export type Ingredient = {
|
||||||
IngredientId: number,
|
ingredientId: number,
|
||||||
Name: string,
|
name: string,
|
||||||
Description?: string
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RecipeIngredient = {
|
export type RecipeIngredient = {
|
||||||
RecipeIngredientId: number,
|
recipeIngredientId: number,
|
||||||
RecipeId: number,
|
recipeId: number,
|
||||||
IngredientId: number,
|
ingredientId: number,
|
||||||
Amount?: number,
|
amount?: number,
|
||||||
Unit?: string,
|
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 (
|
return (
|
||||||
typeof input == 'object'
|
typeof input == 'object'
|
||||||
&& input != null
|
&& input != null
|
||||||
&& 'RecipeId' in input
|
&& 'recipeId' in input
|
||||||
&& 'Name' in input
|
&& 'name' in input
|
||||||
&& 'ShortDescription' in input
|
&& 'shortDescription' in input
|
||||||
&& (input as Recipe).RecipeId != undefined
|
&& (input as Recipe).recipeId != undefined
|
||||||
&& (input as Recipe).Name != undefined
|
&& (input as Recipe).name != undefined
|
||||||
&& (input as Recipe).ShortDescription != undefined
|
&& (input as Recipe).shortDescription != undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,10 +25,10 @@ export function isIngredient(input: unknown): input is Ingredient {
|
|||||||
return (
|
return (
|
||||||
typeof input == 'object'
|
typeof input == 'object'
|
||||||
&& input != null
|
&& input != null
|
||||||
&& 'IngredientId' in input
|
&& 'ingredientId' in input
|
||||||
&& 'Name' in input
|
&& 'name' in input
|
||||||
&& (input as Ingredient).IngredientId != undefined
|
&& (input as Ingredient).ingredientId != undefined
|
||||||
&& (input as Ingredient).Name != undefined
|
&& (input as Ingredient).name != undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user