diff --git a/admin-ui/.dockerignore b/admin-ui/.dockerignore
new file mode 100644
index 0000000..1194b4f
--- /dev/null
+++ b/admin-ui/.dockerignore
@@ -0,0 +1,7 @@
+.dockerignore
+docker-compose.yml
+Dockerfile
+build/
+node_modules
+.env
+.gitignore
diff --git a/admin-ui/.env b/admin-ui/.env
new file mode 100644
index 0000000..4eef91c
--- /dev/null
+++ b/admin-ui/.env
@@ -0,0 +1,2 @@
+PORT=3001
+REACT_APP_SERVER_URL=http://localhost:3000
\ No newline at end of file
diff --git a/admin-ui/.gitignore b/admin-ui/.gitignore
new file mode 100644
index 0000000..4d29575
--- /dev/null
+++ b/admin-ui/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/admin-ui/Dockerfile b/admin-ui/Dockerfile
new file mode 100644
index 0000000..f7f0b86
--- /dev/null
+++ b/admin-ui/Dockerfile
@@ -0,0 +1,23 @@
+FROM node:16.16 AS base
+
+ARG REACT_APP_SERVER_URL
+
+ENV REACT_APP_SERVER_URL=$REACT_APP_SERVER_URL
+
+WORKDIR /app
+
+COPY package.json package-lock.json ./
+
+RUN npm ci
+
+COPY . .
+
+RUN npm run build
+
+FROM nginx:stable-alpine AS prod
+
+COPY --from=base /app/build /usr/share/nginx/html
+
+EXPOSE 80
+
+ENTRYPOINT ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/admin-ui/package.json b/admin-ui/package.json
new file mode 100644
index 0000000..f564f91
--- /dev/null
+++ b/admin-ui/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "@sample-service/admin",
+ "private": true,
+ "dependencies": {
+ "@apollo/client": "3.6.9",
+ "@material-ui/core": "4.12.4",
+ "graphql": "15.6.1",
+ "lodash": "4.17.21",
+ "pluralize": "8.0.0",
+ "ra-data-graphql-amplication": "0.0.13",
+ "react": "16.14.0",
+ "react-admin": "3.19.11",
+ "react-dom": "16.14.0",
+ "react-scripts": "5.0.0",
+ "sass": "^1.39.0",
+ "web-vitals": "1.1.2"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject",
+ "docker:build": "docker build ."
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@testing-library/jest-dom": "5.14.1",
+ "@testing-library/react": "11.2.7",
+ "@testing-library/user-event": "13.2.0",
+ "@types/jest": "26.0.16",
+ "@types/lodash": "4.14.178",
+ "@types/node": "12.20.16",
+ "@types/react": "16.14.11",
+ "@types/react-dom": "17.0.0",
+ "type-fest": "0.13.1",
+ "typescript": "4.2.4"
+ }
+}
\ No newline at end of file
diff --git a/admin-ui/public/favicon.ico b/admin-ui/public/favicon.ico
new file mode 100644
index 0000000..fcbdcf2
Binary files /dev/null and b/admin-ui/public/favicon.ico differ
diff --git a/admin-ui/public/index.html b/admin-ui/public/index.html
new file mode 100644
index 0000000..da8a620
--- /dev/null
+++ b/admin-ui/public/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Sample service
+
+
+
+
+
+
+
diff --git a/admin-ui/public/logo192.png b/admin-ui/public/logo192.png
new file mode 100644
index 0000000..1918ff2
Binary files /dev/null and b/admin-ui/public/logo192.png differ
diff --git a/admin-ui/public/logo512.png b/admin-ui/public/logo512.png
new file mode 100644
index 0000000..7e7dc74
Binary files /dev/null and b/admin-ui/public/logo512.png differ
diff --git a/admin-ui/public/manifest.json b/admin-ui/public/manifest.json
new file mode 100644
index 0000000..a108a39
--- /dev/null
+++ b/admin-ui/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "Sample service",
+ "name": "Sample service",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
\ No newline at end of file
diff --git a/admin-ui/public/robots.txt b/admin-ui/public/robots.txt
new file mode 100644
index 0000000..e9e57dc
--- /dev/null
+++ b/admin-ui/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/admin-ui/src/App.scss b/admin-ui/src/App.scss
new file mode 100644
index 0000000..4c1cbb0
--- /dev/null
+++ b/admin-ui/src/App.scss
@@ -0,0 +1,59 @@
+// .App {
+// .MuiAppBar-colorSecondary {
+// background-color: black;
+
+// .RaAppBar-menuButton-13 {
+// background-color: yellow;
+// }
+// }
+
+// .MuiDrawer-paper {
+// background-color: red;
+
+// .MuiListItemIcon-root {
+// color: white;
+// }
+// }
+
+// .MuiButton-textPrimary {
+// background-color: purple;
+// margin: 0 0.5rem;
+// color: white;
+// padding: 0.5rem 1rem;
+
+// &:hover {
+// background-color: blue;
+// }
+// }
+
+// .MuiTableRow-head {
+// .MuiTableCell-head {
+// background-color: black;
+// color: white;
+// }
+
+// .MuiTableSortLabel-root {
+// &:hover {
+// color: red;
+
+// .MuiTableSortLabel-icon {
+// color: red !important;
+// }
+// }
+// .MuiTableSortLabel-icon {
+// color: white !important;
+// }
+// }
+// .MuiTableSortLabel-active {
+// color: green;
+
+// .MuiTableSortLabel-icon {
+// color: green !important;
+// }
+// }
+// }
+
+// .MuiFormLabel-root {
+// color: magenta;
+// }
+// }
diff --git a/admin-ui/src/App.tsx b/admin-ui/src/App.tsx
new file mode 100644
index 0000000..bb46e7b
--- /dev/null
+++ b/admin-ui/src/App.tsx
@@ -0,0 +1,94 @@
+import React, { useEffect, useState } from "react";
+import { Admin, DataProvider, Resource } from "react-admin";
+import buildGraphQLProvider from "./data-provider/graphqlDataProvider";
+import { theme } from "./theme/theme";
+import Login from "./Login";
+import "./App.scss";
+import Dashboard from "./pages/Dashboard";
+import { UserList } from "./user/UserList";
+import { UserCreate } from "./user/UserCreate";
+import { UserEdit } from "./user/UserEdit";
+import { UserShow } from "./user/UserShow";
+import { OrderList } from "./order/OrderList";
+import { OrderCreate } from "./order/OrderCreate";
+import { OrderEdit } from "./order/OrderEdit";
+import { OrderShow } from "./order/OrderShow";
+import { CustomerList } from "./customer/CustomerList";
+import { CustomerCreate } from "./customer/CustomerCreate";
+import { CustomerEdit } from "./customer/CustomerEdit";
+import { CustomerShow } from "./customer/CustomerShow";
+import { AddressList } from "./address/AddressList";
+import { AddressCreate } from "./address/AddressCreate";
+import { AddressEdit } from "./address/AddressEdit";
+import { AddressShow } from "./address/AddressShow";
+import { ProductList } from "./product/ProductList";
+import { ProductCreate } from "./product/ProductCreate";
+import { ProductEdit } from "./product/ProductEdit";
+import { ProductShow } from "./product/ProductShow";
+import { jwtAuthProvider } from "./auth-provider/ra-auth-jwt";
+
+const App = (): React.ReactElement => {
+ const [dataProvider, setDataProvider] = useState(null);
+ useEffect(() => {
+ buildGraphQLProvider
+ .then((provider: any) => {
+ setDataProvider(() => provider);
+ })
+ .catch((error: any) => {
+ console.log(error);
+ });
+ }, []);
+ if (!dataProvider) {
+ return Loading
;
+ }
+ return (
+
+ );
+};
+
+export default App;
diff --git a/admin-ui/src/Components/Pagination.tsx b/admin-ui/src/Components/Pagination.tsx
new file mode 100644
index 0000000..2de2ebf
--- /dev/null
+++ b/admin-ui/src/Components/Pagination.tsx
@@ -0,0 +1,10 @@
+import React from "react";
+import { Pagination as RAPagination, PaginationProps } from "react-admin";
+
+const PAGINATION_OPTIONS = [10, 25, 50, 100, 200];
+
+const Pagination = (props: PaginationProps) => (
+
+);
+
+export default Pagination;
diff --git a/admin-ui/src/Login.tsx b/admin-ui/src/Login.tsx
new file mode 100644
index 0000000..79dd07e
--- /dev/null
+++ b/admin-ui/src/Login.tsx
@@ -0,0 +1,117 @@
+import * as React from "react";
+import { useState } from "react";
+import { useLogin, useNotify, Notification, defaultTheme } from "react-admin";
+import { ThemeProvider } from "@material-ui/styles";
+import { createTheme } from "@material-ui/core/styles";
+import { Button } from "@material-ui/core";
+import "./login.scss";
+
+const CLASS_NAME = "login-page";
+
+const Login = ({ theme }: any) => {
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const login = useLogin();
+ const notify = useNotify();
+ const BASE_URI = process.env.REACT_APP_SERVER_URL;
+ const submit = (e: any) => {
+ e.preventDefault();
+ login({ username, password }).catch(() =>
+ notify("Invalid email or password")
+ );
+ };
+
+ return (
+
+
+
+
+
+
Connect via GraphQL
+
+ Connect to the server using GraphQL API with a complete and
+ understandable description of the data in your API
+
+
+
+
+
+
Admin UI
+
+ Sign in to a React-Admin client with ready-made forms for creating
+ and editing all the data models of your application.
+
+
+
+
+
+
Connect via REST API
+
+ Connect to the server using REST API with a built-in Swagger
+ documentation
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Login;
diff --git a/admin-ui/src/address/AddressCreate.tsx b/admin-ui/src/address/AddressCreate.tsx
new file mode 100644
index 0000000..16376b4
--- /dev/null
+++ b/admin-ui/src/address/AddressCreate.tsx
@@ -0,0 +1,35 @@
+import * as React from "react";
+
+import {
+ Create,
+ SimpleForm,
+ CreateProps,
+ TextInput,
+ ReferenceArrayInput,
+ SelectArrayInput,
+ NumberInput,
+} from "react-admin";
+
+import { CustomerTitle } from "../customer/CustomerTitle";
+
+export const AddressCreate = (props: CreateProps): React.ReactElement => {
+ return (
+
+
+
+
+
+ value && value.map((v: any) => ({ id: v }))}
+ format={(value: any) => value && value.map((v: any) => v.id)}
+ >
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/address/AddressEdit.tsx b/admin-ui/src/address/AddressEdit.tsx
new file mode 100644
index 0000000..133e708
--- /dev/null
+++ b/admin-ui/src/address/AddressEdit.tsx
@@ -0,0 +1,35 @@
+import * as React from "react";
+
+import {
+ Edit,
+ SimpleForm,
+ EditProps,
+ TextInput,
+ ReferenceArrayInput,
+ SelectArrayInput,
+ NumberInput,
+} from "react-admin";
+
+import { CustomerTitle } from "../customer/CustomerTitle";
+
+export const AddressEdit = (props: EditProps): React.ReactElement => {
+ return (
+
+
+
+
+
+ value && value.map((v: any) => ({ id: v }))}
+ format={(value: any) => value && value.map((v: any) => v.id)}
+ >
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/address/AddressList.tsx b/admin-ui/src/address/AddressList.tsx
new file mode 100644
index 0000000..40adbfd
--- /dev/null
+++ b/admin-ui/src/address/AddressList.tsx
@@ -0,0 +1,26 @@
+import * as React from "react";
+import { List, Datagrid, ListProps, TextField, DateField } from "react-admin";
+import Pagination from "../Components/Pagination";
+
+export const AddressList = (props: ListProps): React.ReactElement => {
+ return (
+
}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/address/AddressShow.tsx b/admin-ui/src/address/AddressShow.tsx
new file mode 100644
index 0000000..55bab7b
--- /dev/null
+++ b/admin-ui/src/address/AddressShow.tsx
@@ -0,0 +1,53 @@
+import * as React from "react";
+
+import {
+ Show,
+ SimpleShowLayout,
+ ShowProps,
+ TextField,
+ DateField,
+ ReferenceManyField,
+ Datagrid,
+ ReferenceField,
+} from "react-admin";
+
+import { ADDRESS_TITLE_FIELD } from "./AddressTitle";
+
+export const AddressShow = (props: ShowProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/address/AddressTitle.ts b/admin-ui/src/address/AddressTitle.ts
new file mode 100644
index 0000000..2b98846
--- /dev/null
+++ b/admin-ui/src/address/AddressTitle.ts
@@ -0,0 +1,7 @@
+import { Address as TAddress } from "../api/address/Address";
+
+export const ADDRESS_TITLE_FIELD = "address_1";
+
+export const AddressTitle = (record: TAddress): string => {
+ return record.address_1 || record.id;
+};
diff --git a/admin-ui/src/api/address/Address.ts b/admin-ui/src/api/address/Address.ts
new file mode 100644
index 0000000..30d86eb
--- /dev/null
+++ b/admin-ui/src/api/address/Address.ts
@@ -0,0 +1,13 @@
+import { Customer } from "../customer/Customer";
+
+export type Address = {
+ address_1: string | null;
+ address_2: string | null;
+ city: string | null;
+ createdAt: Date;
+ customers?: Array;
+ id: string;
+ state: string | null;
+ updatedAt: Date;
+ zip: number | null;
+};
diff --git a/admin-ui/src/api/address/AddressCreateInput.ts b/admin-ui/src/api/address/AddressCreateInput.ts
new file mode 100644
index 0000000..f1a96b7
--- /dev/null
+++ b/admin-ui/src/api/address/AddressCreateInput.ts
@@ -0,0 +1,10 @@
+import { CustomerCreateNestedManyWithoutAddressesInput } from "./CustomerCreateNestedManyWithoutAddressesInput";
+
+export type AddressCreateInput = {
+ address_1?: string | null;
+ address_2?: string | null;
+ city?: string | null;
+ customers?: CustomerCreateNestedManyWithoutAddressesInput;
+ state?: string | null;
+ zip?: number | null;
+};
diff --git a/admin-ui/src/api/address/AddressFindManyArgs.ts b/admin-ui/src/api/address/AddressFindManyArgs.ts
new file mode 100644
index 0000000..459afb2
--- /dev/null
+++ b/admin-ui/src/api/address/AddressFindManyArgs.ts
@@ -0,0 +1,9 @@
+import { AddressWhereInput } from "./AddressWhereInput";
+import { AddressOrderByInput } from "./AddressOrderByInput";
+
+export type AddressFindManyArgs = {
+ where?: AddressWhereInput;
+ orderBy?: Array;
+ skip?: number;
+ take?: number;
+};
diff --git a/admin-ui/src/api/address/AddressFindUniqueArgs.ts b/admin-ui/src/api/address/AddressFindUniqueArgs.ts
new file mode 100644
index 0000000..42f263f
--- /dev/null
+++ b/admin-ui/src/api/address/AddressFindUniqueArgs.ts
@@ -0,0 +1,5 @@
+import { AddressWhereUniqueInput } from "./AddressWhereUniqueInput";
+
+export type AddressFindUniqueArgs = {
+ where: AddressWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/address/AddressListRelationFilter.ts b/admin-ui/src/api/address/AddressListRelationFilter.ts
new file mode 100644
index 0000000..7a29026
--- /dev/null
+++ b/admin-ui/src/api/address/AddressListRelationFilter.ts
@@ -0,0 +1,7 @@
+import { AddressWhereInput } from "./AddressWhereInput";
+
+export type AddressListRelationFilter = {
+ every?: AddressWhereInput;
+ some?: AddressWhereInput;
+ none?: AddressWhereInput;
+};
diff --git a/admin-ui/src/api/address/AddressOrderByInput.ts b/admin-ui/src/api/address/AddressOrderByInput.ts
new file mode 100644
index 0000000..a224f92
--- /dev/null
+++ b/admin-ui/src/api/address/AddressOrderByInput.ts
@@ -0,0 +1,12 @@
+import { SortOrder } from "../../util/SortOrder";
+
+export type AddressOrderByInput = {
+ address_1?: SortOrder;
+ address_2?: SortOrder;
+ city?: SortOrder;
+ createdAt?: SortOrder;
+ id?: SortOrder;
+ state?: SortOrder;
+ updatedAt?: SortOrder;
+ zip?: SortOrder;
+};
diff --git a/admin-ui/src/api/address/AddressUpdateInput.ts b/admin-ui/src/api/address/AddressUpdateInput.ts
new file mode 100644
index 0000000..e6d187b
--- /dev/null
+++ b/admin-ui/src/api/address/AddressUpdateInput.ts
@@ -0,0 +1,10 @@
+import { CustomerUpdateManyWithoutAddressesInput } from "./CustomerUpdateManyWithoutAddressesInput";
+
+export type AddressUpdateInput = {
+ address_1?: string | null;
+ address_2?: string | null;
+ city?: string | null;
+ customers?: CustomerUpdateManyWithoutAddressesInput;
+ state?: string | null;
+ zip?: number | null;
+};
diff --git a/admin-ui/src/api/address/AddressWhereInput.ts b/admin-ui/src/api/address/AddressWhereInput.ts
new file mode 100644
index 0000000..a5606f7
--- /dev/null
+++ b/admin-ui/src/api/address/AddressWhereInput.ts
@@ -0,0 +1,14 @@
+import { StringNullableFilter } from "../../util/StringNullableFilter";
+import { CustomerListRelationFilter } from "../customer/CustomerListRelationFilter";
+import { StringFilter } from "../../util/StringFilter";
+import { IntNullableFilter } from "../../util/IntNullableFilter";
+
+export type AddressWhereInput = {
+ address_1?: StringNullableFilter;
+ address_2?: StringNullableFilter;
+ city?: StringNullableFilter;
+ customers?: CustomerListRelationFilter;
+ id?: StringFilter;
+ state?: StringNullableFilter;
+ zip?: IntNullableFilter;
+};
diff --git a/admin-ui/src/api/address/AddressWhereUniqueInput.ts b/admin-ui/src/api/address/AddressWhereUniqueInput.ts
new file mode 100644
index 0000000..1eb84ac
--- /dev/null
+++ b/admin-ui/src/api/address/AddressWhereUniqueInput.ts
@@ -0,0 +1,3 @@
+export type AddressWhereUniqueInput = {
+ id: string;
+};
diff --git a/admin-ui/src/api/address/CreateAddressArgs.ts b/admin-ui/src/api/address/CreateAddressArgs.ts
new file mode 100644
index 0000000..ca4273a
--- /dev/null
+++ b/admin-ui/src/api/address/CreateAddressArgs.ts
@@ -0,0 +1,5 @@
+import { AddressCreateInput } from "./AddressCreateInput";
+
+export type CreateAddressArgs = {
+ data: AddressCreateInput;
+};
diff --git a/admin-ui/src/api/address/CustomerCreateNestedManyWithoutAddressesInput.ts b/admin-ui/src/api/address/CustomerCreateNestedManyWithoutAddressesInput.ts
new file mode 100644
index 0000000..d7ce280
--- /dev/null
+++ b/admin-ui/src/api/address/CustomerCreateNestedManyWithoutAddressesInput.ts
@@ -0,0 +1,5 @@
+import { CustomerWhereUniqueInput } from "../customer/CustomerWhereUniqueInput";
+
+export type CustomerCreateNestedManyWithoutAddressesInput = {
+ connect?: Array;
+};
diff --git a/admin-ui/src/api/address/CustomerUpdateManyWithoutAddressesInput.ts b/admin-ui/src/api/address/CustomerUpdateManyWithoutAddressesInput.ts
new file mode 100644
index 0000000..1f28101
--- /dev/null
+++ b/admin-ui/src/api/address/CustomerUpdateManyWithoutAddressesInput.ts
@@ -0,0 +1,7 @@
+import { CustomerWhereUniqueInput } from "../customer/CustomerWhereUniqueInput";
+
+export type CustomerUpdateManyWithoutAddressesInput = {
+ connect?: Array;
+ disconnect?: Array;
+ set?: Array;
+};
diff --git a/admin-ui/src/api/address/DeleteAddressArgs.ts b/admin-ui/src/api/address/DeleteAddressArgs.ts
new file mode 100644
index 0000000..14f1650
--- /dev/null
+++ b/admin-ui/src/api/address/DeleteAddressArgs.ts
@@ -0,0 +1,5 @@
+import { AddressWhereUniqueInput } from "./AddressWhereUniqueInput";
+
+export type DeleteAddressArgs = {
+ where: AddressWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/address/UpdateAddressArgs.ts b/admin-ui/src/api/address/UpdateAddressArgs.ts
new file mode 100644
index 0000000..181144d
--- /dev/null
+++ b/admin-ui/src/api/address/UpdateAddressArgs.ts
@@ -0,0 +1,7 @@
+import { AddressWhereUniqueInput } from "./AddressWhereUniqueInput";
+import { AddressUpdateInput } from "./AddressUpdateInput";
+
+export type UpdateAddressArgs = {
+ where: AddressWhereUniqueInput;
+ data: AddressUpdateInput;
+};
diff --git a/admin-ui/src/api/customer/CreateCustomerArgs.ts b/admin-ui/src/api/customer/CreateCustomerArgs.ts
new file mode 100644
index 0000000..216376d
--- /dev/null
+++ b/admin-ui/src/api/customer/CreateCustomerArgs.ts
@@ -0,0 +1,5 @@
+import { CustomerCreateInput } from "./CustomerCreateInput";
+
+export type CreateCustomerArgs = {
+ data: CustomerCreateInput;
+};
diff --git a/admin-ui/src/api/customer/Customer.ts b/admin-ui/src/api/customer/Customer.ts
new file mode 100644
index 0000000..d6308df
--- /dev/null
+++ b/admin-ui/src/api/customer/Customer.ts
@@ -0,0 +1,14 @@
+import { Address } from "../address/Address";
+import { Order } from "../order/Order";
+
+export type Customer = {
+ address?: Address | null;
+ createdAt: Date;
+ email: string | null;
+ firstName: string | null;
+ id: string;
+ lastName: string | null;
+ orders?: Array;
+ phone: string | null;
+ updatedAt: Date;
+};
diff --git a/admin-ui/src/api/customer/CustomerCreateInput.ts b/admin-ui/src/api/customer/CustomerCreateInput.ts
new file mode 100644
index 0000000..948779f
--- /dev/null
+++ b/admin-ui/src/api/customer/CustomerCreateInput.ts
@@ -0,0 +1,11 @@
+import { AddressWhereUniqueInput } from "../address/AddressWhereUniqueInput";
+import { OrderCreateNestedManyWithoutCustomersInput } from "./OrderCreateNestedManyWithoutCustomersInput";
+
+export type CustomerCreateInput = {
+ address?: AddressWhereUniqueInput | null;
+ email?: string | null;
+ firstName?: string | null;
+ lastName?: string | null;
+ orders?: OrderCreateNestedManyWithoutCustomersInput;
+ phone?: string | null;
+};
diff --git a/admin-ui/src/api/customer/CustomerFindManyArgs.ts b/admin-ui/src/api/customer/CustomerFindManyArgs.ts
new file mode 100644
index 0000000..e1e7b0a
--- /dev/null
+++ b/admin-ui/src/api/customer/CustomerFindManyArgs.ts
@@ -0,0 +1,9 @@
+import { CustomerWhereInput } from "./CustomerWhereInput";
+import { CustomerOrderByInput } from "./CustomerOrderByInput";
+
+export type CustomerFindManyArgs = {
+ where?: CustomerWhereInput;
+ orderBy?: Array;
+ skip?: number;
+ take?: number;
+};
diff --git a/admin-ui/src/api/customer/CustomerFindUniqueArgs.ts b/admin-ui/src/api/customer/CustomerFindUniqueArgs.ts
new file mode 100644
index 0000000..c0b9e44
--- /dev/null
+++ b/admin-ui/src/api/customer/CustomerFindUniqueArgs.ts
@@ -0,0 +1,5 @@
+import { CustomerWhereUniqueInput } from "./CustomerWhereUniqueInput";
+
+export type CustomerFindUniqueArgs = {
+ where: CustomerWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/customer/CustomerListRelationFilter.ts b/admin-ui/src/api/customer/CustomerListRelationFilter.ts
new file mode 100644
index 0000000..64514f8
--- /dev/null
+++ b/admin-ui/src/api/customer/CustomerListRelationFilter.ts
@@ -0,0 +1,7 @@
+import { CustomerWhereInput } from "./CustomerWhereInput";
+
+export type CustomerListRelationFilter = {
+ every?: CustomerWhereInput;
+ some?: CustomerWhereInput;
+ none?: CustomerWhereInput;
+};
diff --git a/admin-ui/src/api/customer/CustomerOrderByInput.ts b/admin-ui/src/api/customer/CustomerOrderByInput.ts
new file mode 100644
index 0000000..a495719
--- /dev/null
+++ b/admin-ui/src/api/customer/CustomerOrderByInput.ts
@@ -0,0 +1,12 @@
+import { SortOrder } from "../../util/SortOrder";
+
+export type CustomerOrderByInput = {
+ addressId?: SortOrder;
+ createdAt?: SortOrder;
+ email?: SortOrder;
+ firstName?: SortOrder;
+ id?: SortOrder;
+ lastName?: SortOrder;
+ phone?: SortOrder;
+ updatedAt?: SortOrder;
+};
diff --git a/admin-ui/src/api/customer/CustomerUpdateInput.ts b/admin-ui/src/api/customer/CustomerUpdateInput.ts
new file mode 100644
index 0000000..d1c72a3
--- /dev/null
+++ b/admin-ui/src/api/customer/CustomerUpdateInput.ts
@@ -0,0 +1,11 @@
+import { AddressWhereUniqueInput } from "../address/AddressWhereUniqueInput";
+import { OrderUpdateManyWithoutCustomersInput } from "./OrderUpdateManyWithoutCustomersInput";
+
+export type CustomerUpdateInput = {
+ address?: AddressWhereUniqueInput | null;
+ email?: string | null;
+ firstName?: string | null;
+ lastName?: string | null;
+ orders?: OrderUpdateManyWithoutCustomersInput;
+ phone?: string | null;
+};
diff --git a/admin-ui/src/api/customer/CustomerWhereInput.ts b/admin-ui/src/api/customer/CustomerWhereInput.ts
new file mode 100644
index 0000000..5bff5b5
--- /dev/null
+++ b/admin-ui/src/api/customer/CustomerWhereInput.ts
@@ -0,0 +1,14 @@
+import { AddressWhereUniqueInput } from "../address/AddressWhereUniqueInput";
+import { StringNullableFilter } from "../../util/StringNullableFilter";
+import { StringFilter } from "../../util/StringFilter";
+import { OrderListRelationFilter } from "../order/OrderListRelationFilter";
+
+export type CustomerWhereInput = {
+ address?: AddressWhereUniqueInput;
+ email?: StringNullableFilter;
+ firstName?: StringNullableFilter;
+ id?: StringFilter;
+ lastName?: StringNullableFilter;
+ orders?: OrderListRelationFilter;
+ phone?: StringNullableFilter;
+};
diff --git a/admin-ui/src/api/customer/CustomerWhereUniqueInput.ts b/admin-ui/src/api/customer/CustomerWhereUniqueInput.ts
new file mode 100644
index 0000000..87b1d6e
--- /dev/null
+++ b/admin-ui/src/api/customer/CustomerWhereUniqueInput.ts
@@ -0,0 +1,3 @@
+export type CustomerWhereUniqueInput = {
+ id: string;
+};
diff --git a/admin-ui/src/api/customer/DeleteCustomerArgs.ts b/admin-ui/src/api/customer/DeleteCustomerArgs.ts
new file mode 100644
index 0000000..64bdd7b
--- /dev/null
+++ b/admin-ui/src/api/customer/DeleteCustomerArgs.ts
@@ -0,0 +1,5 @@
+import { CustomerWhereUniqueInput } from "./CustomerWhereUniqueInput";
+
+export type DeleteCustomerArgs = {
+ where: CustomerWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/customer/OrderCreateNestedManyWithoutCustomersInput.ts b/admin-ui/src/api/customer/OrderCreateNestedManyWithoutCustomersInput.ts
new file mode 100644
index 0000000..e205569
--- /dev/null
+++ b/admin-ui/src/api/customer/OrderCreateNestedManyWithoutCustomersInput.ts
@@ -0,0 +1,5 @@
+import { OrderWhereUniqueInput } from "../order/OrderWhereUniqueInput";
+
+export type OrderCreateNestedManyWithoutCustomersInput = {
+ connect?: Array;
+};
diff --git a/admin-ui/src/api/customer/OrderUpdateManyWithoutCustomersInput.ts b/admin-ui/src/api/customer/OrderUpdateManyWithoutCustomersInput.ts
new file mode 100644
index 0000000..ff95304
--- /dev/null
+++ b/admin-ui/src/api/customer/OrderUpdateManyWithoutCustomersInput.ts
@@ -0,0 +1,7 @@
+import { OrderWhereUniqueInput } from "../order/OrderWhereUniqueInput";
+
+export type OrderUpdateManyWithoutCustomersInput = {
+ connect?: Array;
+ disconnect?: Array;
+ set?: Array;
+};
diff --git a/admin-ui/src/api/customer/UpdateCustomerArgs.ts b/admin-ui/src/api/customer/UpdateCustomerArgs.ts
new file mode 100644
index 0000000..65a7b32
--- /dev/null
+++ b/admin-ui/src/api/customer/UpdateCustomerArgs.ts
@@ -0,0 +1,7 @@
+import { CustomerWhereUniqueInput } from "./CustomerWhereUniqueInput";
+import { CustomerUpdateInput } from "./CustomerUpdateInput";
+
+export type UpdateCustomerArgs = {
+ where: CustomerWhereUniqueInput;
+ data: CustomerUpdateInput;
+};
diff --git a/admin-ui/src/api/order/CreateOrderArgs.ts b/admin-ui/src/api/order/CreateOrderArgs.ts
new file mode 100644
index 0000000..ae92a61
--- /dev/null
+++ b/admin-ui/src/api/order/CreateOrderArgs.ts
@@ -0,0 +1,5 @@
+import { OrderCreateInput } from "./OrderCreateInput";
+
+export type CreateOrderArgs = {
+ data: OrderCreateInput;
+};
diff --git a/admin-ui/src/api/order/DeleteOrderArgs.ts b/admin-ui/src/api/order/DeleteOrderArgs.ts
new file mode 100644
index 0000000..72c9a43
--- /dev/null
+++ b/admin-ui/src/api/order/DeleteOrderArgs.ts
@@ -0,0 +1,5 @@
+import { OrderWhereUniqueInput } from "./OrderWhereUniqueInput";
+
+export type DeleteOrderArgs = {
+ where: OrderWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/order/Order.ts b/admin-ui/src/api/order/Order.ts
new file mode 100644
index 0000000..6216bd3
--- /dev/null
+++ b/admin-ui/src/api/order/Order.ts
@@ -0,0 +1,13 @@
+import { Customer } from "../customer/Customer";
+import { Product } from "../product/Product";
+
+export type Order = {
+ createdAt: Date;
+ customer?: Customer | null;
+ discount: number | null;
+ id: string;
+ product?: Product | null;
+ quantity: number | null;
+ totalPrice: number | null;
+ updatedAt: Date;
+};
diff --git a/admin-ui/src/api/order/OrderCreateInput.ts b/admin-ui/src/api/order/OrderCreateInput.ts
new file mode 100644
index 0000000..991131d
--- /dev/null
+++ b/admin-ui/src/api/order/OrderCreateInput.ts
@@ -0,0 +1,10 @@
+import { CustomerWhereUniqueInput } from "../customer/CustomerWhereUniqueInput";
+import { ProductWhereUniqueInput } from "../product/ProductWhereUniqueInput";
+
+export type OrderCreateInput = {
+ customer?: CustomerWhereUniqueInput | null;
+ discount?: number | null;
+ product?: ProductWhereUniqueInput | null;
+ quantity?: number | null;
+ totalPrice?: number | null;
+};
diff --git a/admin-ui/src/api/order/OrderFindManyArgs.ts b/admin-ui/src/api/order/OrderFindManyArgs.ts
new file mode 100644
index 0000000..44e4a43
--- /dev/null
+++ b/admin-ui/src/api/order/OrderFindManyArgs.ts
@@ -0,0 +1,9 @@
+import { OrderWhereInput } from "./OrderWhereInput";
+import { OrderOrderByInput } from "./OrderOrderByInput";
+
+export type OrderFindManyArgs = {
+ where?: OrderWhereInput;
+ orderBy?: Array;
+ skip?: number;
+ take?: number;
+};
diff --git a/admin-ui/src/api/order/OrderFindUniqueArgs.ts b/admin-ui/src/api/order/OrderFindUniqueArgs.ts
new file mode 100644
index 0000000..3be5d14
--- /dev/null
+++ b/admin-ui/src/api/order/OrderFindUniqueArgs.ts
@@ -0,0 +1,5 @@
+import { OrderWhereUniqueInput } from "./OrderWhereUniqueInput";
+
+export type OrderFindUniqueArgs = {
+ where: OrderWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/order/OrderListRelationFilter.ts b/admin-ui/src/api/order/OrderListRelationFilter.ts
new file mode 100644
index 0000000..c9b2856
--- /dev/null
+++ b/admin-ui/src/api/order/OrderListRelationFilter.ts
@@ -0,0 +1,7 @@
+import { OrderWhereInput } from "./OrderWhereInput";
+
+export type OrderListRelationFilter = {
+ every?: OrderWhereInput;
+ some?: OrderWhereInput;
+ none?: OrderWhereInput;
+};
diff --git a/admin-ui/src/api/order/OrderOrderByInput.ts b/admin-ui/src/api/order/OrderOrderByInput.ts
new file mode 100644
index 0000000..8a68733
--- /dev/null
+++ b/admin-ui/src/api/order/OrderOrderByInput.ts
@@ -0,0 +1,12 @@
+import { SortOrder } from "../../util/SortOrder";
+
+export type OrderOrderByInput = {
+ createdAt?: SortOrder;
+ customerId?: SortOrder;
+ discount?: SortOrder;
+ id?: SortOrder;
+ productId?: SortOrder;
+ quantity?: SortOrder;
+ totalPrice?: SortOrder;
+ updatedAt?: SortOrder;
+};
diff --git a/admin-ui/src/api/order/OrderUpdateInput.ts b/admin-ui/src/api/order/OrderUpdateInput.ts
new file mode 100644
index 0000000..87fc1dc
--- /dev/null
+++ b/admin-ui/src/api/order/OrderUpdateInput.ts
@@ -0,0 +1,10 @@
+import { CustomerWhereUniqueInput } from "../customer/CustomerWhereUniqueInput";
+import { ProductWhereUniqueInput } from "../product/ProductWhereUniqueInput";
+
+export type OrderUpdateInput = {
+ customer?: CustomerWhereUniqueInput | null;
+ discount?: number | null;
+ product?: ProductWhereUniqueInput | null;
+ quantity?: number | null;
+ totalPrice?: number | null;
+};
diff --git a/admin-ui/src/api/order/OrderWhereInput.ts b/admin-ui/src/api/order/OrderWhereInput.ts
new file mode 100644
index 0000000..5d5d895
--- /dev/null
+++ b/admin-ui/src/api/order/OrderWhereInput.ts
@@ -0,0 +1,14 @@
+import { CustomerWhereUniqueInput } from "../customer/CustomerWhereUniqueInput";
+import { FloatNullableFilter } from "../../util/FloatNullableFilter";
+import { StringFilter } from "../../util/StringFilter";
+import { ProductWhereUniqueInput } from "../product/ProductWhereUniqueInput";
+import { IntNullableFilter } from "../../util/IntNullableFilter";
+
+export type OrderWhereInput = {
+ customer?: CustomerWhereUniqueInput;
+ discount?: FloatNullableFilter;
+ id?: StringFilter;
+ product?: ProductWhereUniqueInput;
+ quantity?: IntNullableFilter;
+ totalPrice?: IntNullableFilter;
+};
diff --git a/admin-ui/src/api/order/OrderWhereUniqueInput.ts b/admin-ui/src/api/order/OrderWhereUniqueInput.ts
new file mode 100644
index 0000000..ea2ede7
--- /dev/null
+++ b/admin-ui/src/api/order/OrderWhereUniqueInput.ts
@@ -0,0 +1,3 @@
+export type OrderWhereUniqueInput = {
+ id: string;
+};
diff --git a/admin-ui/src/api/order/UpdateOrderArgs.ts b/admin-ui/src/api/order/UpdateOrderArgs.ts
new file mode 100644
index 0000000..88d849f
--- /dev/null
+++ b/admin-ui/src/api/order/UpdateOrderArgs.ts
@@ -0,0 +1,7 @@
+import { OrderWhereUniqueInput } from "./OrderWhereUniqueInput";
+import { OrderUpdateInput } from "./OrderUpdateInput";
+
+export type UpdateOrderArgs = {
+ where: OrderWhereUniqueInput;
+ data: OrderUpdateInput;
+};
diff --git a/admin-ui/src/api/product/CreateProductArgs.ts b/admin-ui/src/api/product/CreateProductArgs.ts
new file mode 100644
index 0000000..3e98f36
--- /dev/null
+++ b/admin-ui/src/api/product/CreateProductArgs.ts
@@ -0,0 +1,5 @@
+import { ProductCreateInput } from "./ProductCreateInput";
+
+export type CreateProductArgs = {
+ data: ProductCreateInput;
+};
diff --git a/admin-ui/src/api/product/DeleteProductArgs.ts b/admin-ui/src/api/product/DeleteProductArgs.ts
new file mode 100644
index 0000000..f1f7ba1
--- /dev/null
+++ b/admin-ui/src/api/product/DeleteProductArgs.ts
@@ -0,0 +1,5 @@
+import { ProductWhereUniqueInput } from "./ProductWhereUniqueInput";
+
+export type DeleteProductArgs = {
+ where: ProductWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/product/OrderCreateNestedManyWithoutProductsInput.ts b/admin-ui/src/api/product/OrderCreateNestedManyWithoutProductsInput.ts
new file mode 100644
index 0000000..1b18f00
--- /dev/null
+++ b/admin-ui/src/api/product/OrderCreateNestedManyWithoutProductsInput.ts
@@ -0,0 +1,5 @@
+import { OrderWhereUniqueInput } from "../order/OrderWhereUniqueInput";
+
+export type OrderCreateNestedManyWithoutProductsInput = {
+ connect?: Array;
+};
diff --git a/admin-ui/src/api/product/OrderUpdateManyWithoutProductsInput.ts b/admin-ui/src/api/product/OrderUpdateManyWithoutProductsInput.ts
new file mode 100644
index 0000000..1be365f
--- /dev/null
+++ b/admin-ui/src/api/product/OrderUpdateManyWithoutProductsInput.ts
@@ -0,0 +1,7 @@
+import { OrderWhereUniqueInput } from "../order/OrderWhereUniqueInput";
+
+export type OrderUpdateManyWithoutProductsInput = {
+ connect?: Array;
+ disconnect?: Array;
+ set?: Array;
+};
diff --git a/admin-ui/src/api/product/Product.ts b/admin-ui/src/api/product/Product.ts
new file mode 100644
index 0000000..2d63d9b
--- /dev/null
+++ b/admin-ui/src/api/product/Product.ts
@@ -0,0 +1,11 @@
+import { Order } from "../order/Order";
+
+export type Product = {
+ createdAt: Date;
+ description: string | null;
+ id: string;
+ itemPrice: number | null;
+ name: string | null;
+ orders?: Array;
+ updatedAt: Date;
+};
diff --git a/admin-ui/src/api/product/ProductCreateInput.ts b/admin-ui/src/api/product/ProductCreateInput.ts
new file mode 100644
index 0000000..3018930
--- /dev/null
+++ b/admin-ui/src/api/product/ProductCreateInput.ts
@@ -0,0 +1,8 @@
+import { OrderCreateNestedManyWithoutProductsInput } from "./OrderCreateNestedManyWithoutProductsInput";
+
+export type ProductCreateInput = {
+ description?: string | null;
+ itemPrice?: number | null;
+ name?: string | null;
+ orders?: OrderCreateNestedManyWithoutProductsInput;
+};
diff --git a/admin-ui/src/api/product/ProductFindManyArgs.ts b/admin-ui/src/api/product/ProductFindManyArgs.ts
new file mode 100644
index 0000000..bcaae8a
--- /dev/null
+++ b/admin-ui/src/api/product/ProductFindManyArgs.ts
@@ -0,0 +1,9 @@
+import { ProductWhereInput } from "./ProductWhereInput";
+import { ProductOrderByInput } from "./ProductOrderByInput";
+
+export type ProductFindManyArgs = {
+ where?: ProductWhereInput;
+ orderBy?: Array;
+ skip?: number;
+ take?: number;
+};
diff --git a/admin-ui/src/api/product/ProductFindUniqueArgs.ts b/admin-ui/src/api/product/ProductFindUniqueArgs.ts
new file mode 100644
index 0000000..5f6b0f6
--- /dev/null
+++ b/admin-ui/src/api/product/ProductFindUniqueArgs.ts
@@ -0,0 +1,5 @@
+import { ProductWhereUniqueInput } from "./ProductWhereUniqueInput";
+
+export type ProductFindUniqueArgs = {
+ where: ProductWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/product/ProductListRelationFilter.ts b/admin-ui/src/api/product/ProductListRelationFilter.ts
new file mode 100644
index 0000000..e1c37ca
--- /dev/null
+++ b/admin-ui/src/api/product/ProductListRelationFilter.ts
@@ -0,0 +1,7 @@
+import { ProductWhereInput } from "./ProductWhereInput";
+
+export type ProductListRelationFilter = {
+ every?: ProductWhereInput;
+ some?: ProductWhereInput;
+ none?: ProductWhereInput;
+};
diff --git a/admin-ui/src/api/product/ProductOrderByInput.ts b/admin-ui/src/api/product/ProductOrderByInput.ts
new file mode 100644
index 0000000..01f501a
--- /dev/null
+++ b/admin-ui/src/api/product/ProductOrderByInput.ts
@@ -0,0 +1,10 @@
+import { SortOrder } from "../../util/SortOrder";
+
+export type ProductOrderByInput = {
+ createdAt?: SortOrder;
+ description?: SortOrder;
+ id?: SortOrder;
+ itemPrice?: SortOrder;
+ name?: SortOrder;
+ updatedAt?: SortOrder;
+};
diff --git a/admin-ui/src/api/product/ProductUpdateInput.ts b/admin-ui/src/api/product/ProductUpdateInput.ts
new file mode 100644
index 0000000..dbe5c14
--- /dev/null
+++ b/admin-ui/src/api/product/ProductUpdateInput.ts
@@ -0,0 +1,8 @@
+import { OrderUpdateManyWithoutProductsInput } from "./OrderUpdateManyWithoutProductsInput";
+
+export type ProductUpdateInput = {
+ description?: string | null;
+ itemPrice?: number | null;
+ name?: string | null;
+ orders?: OrderUpdateManyWithoutProductsInput;
+};
diff --git a/admin-ui/src/api/product/ProductWhereInput.ts b/admin-ui/src/api/product/ProductWhereInput.ts
new file mode 100644
index 0000000..a3d834c
--- /dev/null
+++ b/admin-ui/src/api/product/ProductWhereInput.ts
@@ -0,0 +1,12 @@
+import { StringNullableFilter } from "../../util/StringNullableFilter";
+import { StringFilter } from "../../util/StringFilter";
+import { FloatNullableFilter } from "../../util/FloatNullableFilter";
+import { OrderListRelationFilter } from "../order/OrderListRelationFilter";
+
+export type ProductWhereInput = {
+ description?: StringNullableFilter;
+ id?: StringFilter;
+ itemPrice?: FloatNullableFilter;
+ name?: StringNullableFilter;
+ orders?: OrderListRelationFilter;
+};
diff --git a/admin-ui/src/api/product/ProductWhereUniqueInput.ts b/admin-ui/src/api/product/ProductWhereUniqueInput.ts
new file mode 100644
index 0000000..3432b3b
--- /dev/null
+++ b/admin-ui/src/api/product/ProductWhereUniqueInput.ts
@@ -0,0 +1,3 @@
+export type ProductWhereUniqueInput = {
+ id: string;
+};
diff --git a/admin-ui/src/api/product/UpdateProductArgs.ts b/admin-ui/src/api/product/UpdateProductArgs.ts
new file mode 100644
index 0000000..54fba33
--- /dev/null
+++ b/admin-ui/src/api/product/UpdateProductArgs.ts
@@ -0,0 +1,7 @@
+import { ProductWhereUniqueInput } from "./ProductWhereUniqueInput";
+import { ProductUpdateInput } from "./ProductUpdateInput";
+
+export type UpdateProductArgs = {
+ where: ProductWhereUniqueInput;
+ data: ProductUpdateInput;
+};
diff --git a/admin-ui/src/api/user/CreateUserArgs.ts b/admin-ui/src/api/user/CreateUserArgs.ts
new file mode 100644
index 0000000..2a56f0c
--- /dev/null
+++ b/admin-ui/src/api/user/CreateUserArgs.ts
@@ -0,0 +1,5 @@
+import { UserCreateInput } from "./UserCreateInput";
+
+export type CreateUserArgs = {
+ data: UserCreateInput;
+};
diff --git a/admin-ui/src/api/user/DeleteUserArgs.ts b/admin-ui/src/api/user/DeleteUserArgs.ts
new file mode 100644
index 0000000..5f655b8
--- /dev/null
+++ b/admin-ui/src/api/user/DeleteUserArgs.ts
@@ -0,0 +1,5 @@
+import { UserWhereUniqueInput } from "./UserWhereUniqueInput";
+
+export type DeleteUserArgs = {
+ where: UserWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/user/UpdateUserArgs.ts b/admin-ui/src/api/user/UpdateUserArgs.ts
new file mode 100644
index 0000000..30e635e
--- /dev/null
+++ b/admin-ui/src/api/user/UpdateUserArgs.ts
@@ -0,0 +1,7 @@
+import { UserWhereUniqueInput } from "./UserWhereUniqueInput";
+import { UserUpdateInput } from "./UserUpdateInput";
+
+export type UpdateUserArgs = {
+ where: UserWhereUniqueInput;
+ data: UserUpdateInput;
+};
diff --git a/admin-ui/src/api/user/User.ts b/admin-ui/src/api/user/User.ts
new file mode 100644
index 0000000..9c53564
--- /dev/null
+++ b/admin-ui/src/api/user/User.ts
@@ -0,0 +1,11 @@
+import { JsonValue } from "type-fest";
+
+export type User = {
+ createdAt: Date;
+ firstName: string | null;
+ id: string;
+ lastName: string | null;
+ roles: JsonValue;
+ updatedAt: Date;
+ username: string;
+};
diff --git a/admin-ui/src/api/user/UserCreateInput.ts b/admin-ui/src/api/user/UserCreateInput.ts
new file mode 100644
index 0000000..e53c277
--- /dev/null
+++ b/admin-ui/src/api/user/UserCreateInput.ts
@@ -0,0 +1,9 @@
+import { InputJsonValue } from "../../types";
+
+export type UserCreateInput = {
+ firstName?: string | null;
+ lastName?: string | null;
+ password: string;
+ roles: InputJsonValue;
+ username: string;
+};
diff --git a/admin-ui/src/api/user/UserFindManyArgs.ts b/admin-ui/src/api/user/UserFindManyArgs.ts
new file mode 100644
index 0000000..b453f3e
--- /dev/null
+++ b/admin-ui/src/api/user/UserFindManyArgs.ts
@@ -0,0 +1,9 @@
+import { UserWhereInput } from "./UserWhereInput";
+import { UserOrderByInput } from "./UserOrderByInput";
+
+export type UserFindManyArgs = {
+ where?: UserWhereInput;
+ orderBy?: Array;
+ skip?: number;
+ take?: number;
+};
diff --git a/admin-ui/src/api/user/UserFindUniqueArgs.ts b/admin-ui/src/api/user/UserFindUniqueArgs.ts
new file mode 100644
index 0000000..97d18e8
--- /dev/null
+++ b/admin-ui/src/api/user/UserFindUniqueArgs.ts
@@ -0,0 +1,5 @@
+import { UserWhereUniqueInput } from "./UserWhereUniqueInput";
+
+export type UserFindUniqueArgs = {
+ where: UserWhereUniqueInput;
+};
diff --git a/admin-ui/src/api/user/UserListRelationFilter.ts b/admin-ui/src/api/user/UserListRelationFilter.ts
new file mode 100644
index 0000000..4c4d06e
--- /dev/null
+++ b/admin-ui/src/api/user/UserListRelationFilter.ts
@@ -0,0 +1,7 @@
+import { UserWhereInput } from "./UserWhereInput";
+
+export type UserListRelationFilter = {
+ every?: UserWhereInput;
+ some?: UserWhereInput;
+ none?: UserWhereInput;
+};
diff --git a/admin-ui/src/api/user/UserOrderByInput.ts b/admin-ui/src/api/user/UserOrderByInput.ts
new file mode 100644
index 0000000..36cee1e
--- /dev/null
+++ b/admin-ui/src/api/user/UserOrderByInput.ts
@@ -0,0 +1,12 @@
+import { SortOrder } from "../../util/SortOrder";
+
+export type UserOrderByInput = {
+ createdAt?: SortOrder;
+ firstName?: SortOrder;
+ id?: SortOrder;
+ lastName?: SortOrder;
+ password?: SortOrder;
+ roles?: SortOrder;
+ updatedAt?: SortOrder;
+ username?: SortOrder;
+};
diff --git a/admin-ui/src/api/user/UserUpdateInput.ts b/admin-ui/src/api/user/UserUpdateInput.ts
new file mode 100644
index 0000000..f5ee977
--- /dev/null
+++ b/admin-ui/src/api/user/UserUpdateInput.ts
@@ -0,0 +1,9 @@
+import { InputJsonValue } from "../../types";
+
+export type UserUpdateInput = {
+ firstName?: string | null;
+ lastName?: string | null;
+ password?: string;
+ roles?: InputJsonValue;
+ username?: string;
+};
diff --git a/admin-ui/src/api/user/UserWhereInput.ts b/admin-ui/src/api/user/UserWhereInput.ts
new file mode 100644
index 0000000..22c10cb
--- /dev/null
+++ b/admin-ui/src/api/user/UserWhereInput.ts
@@ -0,0 +1,9 @@
+import { StringNullableFilter } from "../../util/StringNullableFilter";
+import { StringFilter } from "../../util/StringFilter";
+
+export type UserWhereInput = {
+ firstName?: StringNullableFilter;
+ id?: StringFilter;
+ lastName?: StringNullableFilter;
+ username?: StringFilter;
+};
diff --git a/admin-ui/src/api/user/UserWhereUniqueInput.ts b/admin-ui/src/api/user/UserWhereUniqueInput.ts
new file mode 100644
index 0000000..309d343
--- /dev/null
+++ b/admin-ui/src/api/user/UserWhereUniqueInput.ts
@@ -0,0 +1,3 @@
+export type UserWhereUniqueInput = {
+ id: string;
+};
diff --git a/admin-ui/src/auth-provider/ra-auth-http.ts b/admin-ui/src/auth-provider/ra-auth-http.ts
new file mode 100644
index 0000000..c6eeba8
--- /dev/null
+++ b/admin-ui/src/auth-provider/ra-auth-http.ts
@@ -0,0 +1,78 @@
+import { gql } from "@apollo/client/core";
+import { AuthProvider } from "react-admin";
+import {
+ CREDENTIALS_LOCAL_STORAGE_ITEM,
+ USER_DATA_LOCAL_STORAGE_ITEM,
+} from "../constants";
+import { Credentials, LoginMutateResult } from "../types";
+import { apolloClient } from "../data-provider/graphqlDataProvider";
+
+const LOGIN = gql`
+ mutation login($username: String!, $password: String!) {
+ login(credentials: { username: $username, password: $password }) {
+ username
+ roles
+ }
+ }
+`;
+
+export const httpAuthProvider: AuthProvider = {
+ login: async (credentials: Credentials) => {
+ const userData = await apolloClient.mutate({
+ mutation: LOGIN,
+ variables: {
+ ...credentials,
+ },
+ });
+
+ if (userData && userData.data?.login.username) {
+ localStorage.setItem(
+ CREDENTIALS_LOCAL_STORAGE_ITEM,
+ createBasicAuthorizationHeader(
+ credentials.username,
+ credentials.password
+ )
+ );
+ localStorage.setItem(
+ USER_DATA_LOCAL_STORAGE_ITEM,
+ JSON.stringify(userData.data)
+ );
+ return Promise.resolve();
+ }
+ return Promise.reject();
+ },
+ logout: () => {
+ localStorage.removeItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
+ return Promise.resolve();
+ },
+ checkError: ({ status }: any) => {
+ if (status === 401 || status === 403) {
+ localStorage.removeItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
+ return Promise.reject();
+ }
+ return Promise.resolve();
+ },
+ checkAuth: () => {
+ return localStorage.getItem(CREDENTIALS_LOCAL_STORAGE_ITEM)
+ ? Promise.resolve()
+ : Promise.reject();
+ },
+ getPermissions: () => Promise.reject("Unknown method"),
+ getIdentity: () => {
+ const str = localStorage.getItem(USER_DATA_LOCAL_STORAGE_ITEM);
+ const userData: LoginMutateResult = JSON.parse(str || "");
+
+ return Promise.resolve({
+ id: userData.login.username,
+ fullName: userData.login.username,
+ avatar: undefined,
+ });
+ },
+};
+
+function createBasicAuthorizationHeader(
+ username: string,
+ password: string
+): string {
+ return `Basic ${btoa(`${username}:${password}`)}`;
+}
diff --git a/admin-ui/src/auth-provider/ra-auth-jwt.ts b/admin-ui/src/auth-provider/ra-auth-jwt.ts
new file mode 100644
index 0000000..c8bcafc
--- /dev/null
+++ b/admin-ui/src/auth-provider/ra-auth-jwt.ts
@@ -0,0 +1,72 @@
+import { gql } from "@apollo/client/core";
+import { AuthProvider } from "react-admin";
+import {
+ CREDENTIALS_LOCAL_STORAGE_ITEM,
+ USER_DATA_LOCAL_STORAGE_ITEM,
+} from "../constants";
+import { Credentials, LoginMutateResult } from "../types";
+import { apolloClient } from "../data-provider/graphqlDataProvider";
+
+const LOGIN = gql`
+ mutation login($username: String!, $password: String!) {
+ login(credentials: { username: $username, password: $password }) {
+ username
+ accessToken
+ }
+ }
+`;
+
+export const jwtAuthProvider: AuthProvider = {
+ login: async (credentials: Credentials) => {
+ const userData = await apolloClient.mutate({
+ mutation: LOGIN,
+ variables: {
+ ...credentials,
+ },
+ });
+
+ if (userData && userData.data?.login.username) {
+ localStorage.setItem(
+ CREDENTIALS_LOCAL_STORAGE_ITEM,
+ createBearerAuthorizationHeader(userData.data.login?.accessToken)
+ );
+ localStorage.setItem(
+ USER_DATA_LOCAL_STORAGE_ITEM,
+ JSON.stringify(userData.data)
+ );
+ return Promise.resolve();
+ }
+ return Promise.reject();
+ },
+ logout: () => {
+ localStorage.removeItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
+ return Promise.resolve();
+ },
+ checkError: ({ status }: any) => {
+ if (status === 401 || status === 403) {
+ localStorage.removeItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
+ return Promise.reject();
+ }
+ return Promise.resolve();
+ },
+ checkAuth: () => {
+ return localStorage.getItem(CREDENTIALS_LOCAL_STORAGE_ITEM)
+ ? Promise.resolve()
+ : Promise.reject();
+ },
+ getPermissions: () => Promise.reject("Unknown method"),
+ getIdentity: () => {
+ const str = localStorage.getItem(USER_DATA_LOCAL_STORAGE_ITEM);
+ const userData: LoginMutateResult = JSON.parse(str || "");
+
+ return Promise.resolve({
+ id: userData.login.username,
+ fullName: userData.login.username,
+ avatar: undefined,
+ });
+ },
+};
+
+export function createBearerAuthorizationHeader(accessToken: string) {
+ return `Bearer ${accessToken}`;
+}
diff --git a/admin-ui/src/auth.ts b/admin-ui/src/auth.ts
new file mode 100644
index 0000000..498b026
--- /dev/null
+++ b/admin-ui/src/auth.ts
@@ -0,0 +1,34 @@
+import { EventEmitter } from "events";
+import { CREDENTIALS_LOCAL_STORAGE_ITEM } from "./constants";
+import { Credentials } from "./types";
+
+const eventEmitter = new EventEmitter();
+
+export function isAuthenticated(): boolean {
+ return Boolean(getCredentials());
+}
+
+export function listen(listener: (authenticated: boolean) => void): void {
+ eventEmitter.on("change", () => {
+ listener(isAuthenticated());
+ });
+}
+
+export function setCredentials(credentials: Credentials) {
+ localStorage.setItem(
+ CREDENTIALS_LOCAL_STORAGE_ITEM,
+ JSON.stringify(credentials)
+ );
+}
+
+export function getCredentials(): Credentials | null {
+ const raw = localStorage.getItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
+ if (raw === null) {
+ return null;
+ }
+ return JSON.parse(raw);
+}
+
+export function removeCredentials(): void {
+ localStorage.removeItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
+}
diff --git a/admin-ui/src/constants.ts b/admin-ui/src/constants.ts
new file mode 100644
index 0000000..4b3ca4b
--- /dev/null
+++ b/admin-ui/src/constants.ts
@@ -0,0 +1,2 @@
+export const CREDENTIALS_LOCAL_STORAGE_ITEM = "credentials";
+export const USER_DATA_LOCAL_STORAGE_ITEM = "userData";
diff --git a/admin-ui/src/customer/CustomerCreate.tsx b/admin-ui/src/customer/CustomerCreate.tsx
new file mode 100644
index 0000000..cbef519
--- /dev/null
+++ b/admin-ui/src/customer/CustomerCreate.tsx
@@ -0,0 +1,39 @@
+import * as React from "react";
+
+import {
+ Create,
+ SimpleForm,
+ CreateProps,
+ ReferenceInput,
+ SelectInput,
+ TextInput,
+ ReferenceArrayInput,
+ SelectArrayInput,
+} from "react-admin";
+
+import { AddressTitle } from "../address/AddressTitle";
+import { OrderTitle } from "../order/OrderTitle";
+
+export const CustomerCreate = (props: CreateProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+ value && value.map((v: any) => ({ id: v }))}
+ format={(value: any) => value && value.map((v: any) => v.id)}
+ >
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/customer/CustomerEdit.tsx b/admin-ui/src/customer/CustomerEdit.tsx
new file mode 100644
index 0000000..d50f928
--- /dev/null
+++ b/admin-ui/src/customer/CustomerEdit.tsx
@@ -0,0 +1,39 @@
+import * as React from "react";
+
+import {
+ Edit,
+ SimpleForm,
+ EditProps,
+ ReferenceInput,
+ SelectInput,
+ TextInput,
+ ReferenceArrayInput,
+ SelectArrayInput,
+} from "react-admin";
+
+import { AddressTitle } from "../address/AddressTitle";
+import { OrderTitle } from "../order/OrderTitle";
+
+export const CustomerEdit = (props: EditProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+ value && value.map((v: any) => ({ id: v }))}
+ format={(value: any) => value && value.map((v: any) => v.id)}
+ >
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/customer/CustomerList.tsx b/admin-ui/src/customer/CustomerList.tsx
new file mode 100644
index 0000000..66e8019
--- /dev/null
+++ b/admin-ui/src/customer/CustomerList.tsx
@@ -0,0 +1,36 @@
+import * as React from "react";
+import {
+ List,
+ Datagrid,
+ ListProps,
+ ReferenceField,
+ TextField,
+ DateField,
+} from "react-admin";
+import Pagination from "../Components/Pagination";
+import { ADDRESS_TITLE_FIELD } from "../address/AddressTitle";
+
+export const CustomerList = (props: ListProps): React.ReactElement => {
+ return (
+
}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/customer/CustomerShow.tsx b/admin-ui/src/customer/CustomerShow.tsx
new file mode 100644
index 0000000..7ad14c2
--- /dev/null
+++ b/admin-ui/src/customer/CustomerShow.tsx
@@ -0,0 +1,63 @@
+import * as React from "react";
+
+import {
+ Show,
+ SimpleShowLayout,
+ ShowProps,
+ ReferenceField,
+ TextField,
+ DateField,
+ ReferenceManyField,
+ Datagrid,
+} from "react-admin";
+
+import { CUSTOMER_TITLE_FIELD } from "./CustomerTitle";
+import { PRODUCT_TITLE_FIELD } from "../product/ProductTitle";
+import { ADDRESS_TITLE_FIELD } from "../address/AddressTitle";
+
+export const CustomerShow = (props: ShowProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/customer/CustomerTitle.ts b/admin-ui/src/customer/CustomerTitle.ts
new file mode 100644
index 0000000..0527c29
--- /dev/null
+++ b/admin-ui/src/customer/CustomerTitle.ts
@@ -0,0 +1,7 @@
+import { Customer as TCustomer } from "../api/customer/Customer";
+
+export const CUSTOMER_TITLE_FIELD = "firstName";
+
+export const CustomerTitle = (record: TCustomer): string => {
+ return record.firstName || record.id;
+};
diff --git a/admin-ui/src/data-provider/graphqlDataProvider.ts b/admin-ui/src/data-provider/graphqlDataProvider.ts
new file mode 100644
index 0000000..3ec4466
--- /dev/null
+++ b/admin-ui/src/data-provider/graphqlDataProvider.ts
@@ -0,0 +1,28 @@
+import buildGraphQLProvider from "ra-data-graphql-amplication";
+import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client";
+import { setContext } from "@apollo/client/link/context";
+import { CREDENTIALS_LOCAL_STORAGE_ITEM } from "../constants";
+
+const httpLink = createHttpLink({
+ uri: `${process.env.REACT_APP_SERVER_URL}/graphql`,
+});
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+const authLink = setContext((_, { headers }) => {
+ const token = localStorage.getItem(CREDENTIALS_LOCAL_STORAGE_ITEM);
+ return {
+ headers: {
+ ...headers,
+ authorization: token ? token : "",
+ },
+ };
+});
+
+export const apolloClient = new ApolloClient({
+ cache: new InMemoryCache(),
+ link: authLink.concat(httpLink),
+});
+
+export default buildGraphQLProvider({
+ client: apolloClient,
+});
diff --git a/admin-ui/src/index.css b/admin-ui/src/index.css
new file mode 100644
index 0000000..8686848
--- /dev/null
+++ b/admin-ui/src/index.css
@@ -0,0 +1,26 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+#root {
+ height: 100vh;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
+ monospace;
+}
+
+.amp-breadcrumbs {
+ padding: var(--default-spacing);
+}
+
+.entity-id {
+ color: var(--primary);
+ text-decoration: underline;
+}
diff --git a/admin-ui/src/index.tsx b/admin-ui/src/index.tsx
new file mode 100644
index 0000000..5e2de69
--- /dev/null
+++ b/admin-ui/src/index.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import "./index.css";
+// @ts-ignore
+// eslint-disable-next-line import/no-unresolved
+import App from "./App";
+import reportWebVitals from "./reportWebVitals";
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById("root")
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/admin-ui/src/login.scss b/admin-ui/src/login.scss
new file mode 100644
index 0000000..667d8d2
--- /dev/null
+++ b/admin-ui/src/login.scss
@@ -0,0 +1,119 @@
+:root {
+ --surface: #15192c; /*dark: black100 */
+ --white: #15192c; /*dark: black100 */
+
+ --black100: #ffffff; /*dark: white */
+ --black90: #b7bac7; /*dark: black10 */
+ --black80: #a3a8b8; /*dark: black20 */
+ --black60: #80869d; /*dark: black30 */
+ --black40: #686f8c; /*dark: black40 */
+ --black30: #515873; /*dark: black50 */
+ --black20: #444b66; /*dark: black60 */
+ --black10: #373d57; /*dark: black70 */
+ --black5: #2c3249; /*dark: black80 */
+ --black2: #22273c; /*dark: black90 */
+
+ --primary: #7950ed;
+}
+
+.login-page {
+ height: 100vh;
+ width: 100%;
+ background-color: var(--surface);
+ color: var(--black100);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+
+ &__wrapper {
+ display: flex;
+ align-items: stretch;
+ justify-content: center;
+ flex-direction: row;
+ }
+
+ &__box {
+ text-align: center;
+ width: 340px;
+ background-color: var(--black2);
+ border-radius: var(--small-border-radius);
+ margin: 1rem;
+ padding: 1rem;
+ border: 1px solid var(--black10);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: stretch;
+
+ h2 {
+ font-size: 18px;
+ }
+ img {
+ width: 48px;
+ }
+
+ &__message {
+ color: var(--black80);
+ font-size: 14px;
+ line-height: 22px;
+ }
+
+ button,
+ .MuiButton-contained {
+ box-sizing: border-box;
+ background-color: var(--primary);
+ width: 300px;
+ margin-top: 0.5rem;
+ margin-bottom: 1rem;
+ margin-top: auto;
+ &:hover,
+ &:active,
+ &:focus {
+ background-color: var(--primary);
+ }
+ }
+ }
+
+ form {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 2rem;
+
+ label {
+ span {
+ display: block;
+ text-align: left;
+ font-size: 12px;
+ color: var(--black60);
+ }
+ }
+
+ input {
+ box-sizing: border-box;
+ background-color: var(--white);
+ border: 1px solid var(--black10);
+ padding: 0.5rem;
+ margin-bottom: 1rem;
+ outline: none;
+ border-radius: var(--small-border-radius);
+ width: 300px;
+ color: var(--black100);
+ &:hover,
+ &:active,
+ &:focus {
+ border: 1px solid var(--black100);
+ }
+ }
+ }
+
+ &__read-more {
+ color: var(--black80);
+ a {
+ color: var(--black100);
+ text-decoration: none;
+ }
+ }
+}
diff --git a/admin-ui/src/order/OrderCreate.tsx b/admin-ui/src/order/OrderCreate.tsx
new file mode 100644
index 0000000..3e73a43
--- /dev/null
+++ b/admin-ui/src/order/OrderCreate.tsx
@@ -0,0 +1,33 @@
+import * as React from "react";
+import {
+ Create,
+ SimpleForm,
+ CreateProps,
+ ReferenceInput,
+ SelectInput,
+ NumberInput,
+} from "react-admin";
+import { CustomerTitle } from "../customer/CustomerTitle";
+import { ProductTitle } from "../product/ProductTitle";
+
+export const OrderCreate = (props: CreateProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/order/OrderEdit.tsx b/admin-ui/src/order/OrderEdit.tsx
new file mode 100644
index 0000000..71a2608
--- /dev/null
+++ b/admin-ui/src/order/OrderEdit.tsx
@@ -0,0 +1,33 @@
+import * as React from "react";
+import {
+ Edit,
+ SimpleForm,
+ EditProps,
+ ReferenceInput,
+ SelectInput,
+ NumberInput,
+} from "react-admin";
+import { CustomerTitle } from "../customer/CustomerTitle";
+import { ProductTitle } from "../product/ProductTitle";
+
+export const OrderEdit = (props: EditProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/order/OrderList.tsx b/admin-ui/src/order/OrderList.tsx
new file mode 100644
index 0000000..31208cb
--- /dev/null
+++ b/admin-ui/src/order/OrderList.tsx
@@ -0,0 +1,43 @@
+import * as React from "react";
+import {
+ List,
+ Datagrid,
+ ListProps,
+ DateField,
+ ReferenceField,
+ TextField,
+} from "react-admin";
+import Pagination from "../Components/Pagination";
+import { CUSTOMER_TITLE_FIELD } from "../customer/CustomerTitle";
+import { PRODUCT_TITLE_FIELD } from "../product/ProductTitle";
+
+export const OrderList = (props: ListProps): React.ReactElement => {
+ return (
+
}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/order/OrderShow.tsx b/admin-ui/src/order/OrderShow.tsx
new file mode 100644
index 0000000..da3429f
--- /dev/null
+++ b/admin-ui/src/order/OrderShow.tsx
@@ -0,0 +1,36 @@
+import * as React from "react";
+import {
+ Show,
+ SimpleShowLayout,
+ ShowProps,
+ DateField,
+ ReferenceField,
+ TextField,
+} from "react-admin";
+import { CUSTOMER_TITLE_FIELD } from "../customer/CustomerTitle";
+import { PRODUCT_TITLE_FIELD } from "../product/ProductTitle";
+
+export const OrderShow = (props: ShowProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/order/OrderTitle.ts b/admin-ui/src/order/OrderTitle.ts
new file mode 100644
index 0000000..71b9151
--- /dev/null
+++ b/admin-ui/src/order/OrderTitle.ts
@@ -0,0 +1,7 @@
+import { Order as TOrder } from "../api/order/Order";
+
+export const ORDER_TITLE_FIELD = "id";
+
+export const OrderTitle = (record: TOrder): string => {
+ return record.id || record.id;
+};
diff --git a/admin-ui/src/pages/Dashboard.tsx b/admin-ui/src/pages/Dashboard.tsx
new file mode 100644
index 0000000..39c4d18
--- /dev/null
+++ b/admin-ui/src/pages/Dashboard.tsx
@@ -0,0 +1,12 @@
+import * as React from "react";
+import Card from "@material-ui/core/Card";
+import CardContent from "@material-ui/core/CardContent";
+import { Title } from "react-admin";
+const Dashboard = () => (
+
+
+ Welcome
+
+);
+
+export default Dashboard;
diff --git a/admin-ui/src/product/ProductCreate.tsx b/admin-ui/src/product/ProductCreate.tsx
new file mode 100644
index 0000000..760fd7d
--- /dev/null
+++ b/admin-ui/src/product/ProductCreate.tsx
@@ -0,0 +1,33 @@
+import * as React from "react";
+
+import {
+ Create,
+ SimpleForm,
+ CreateProps,
+ TextInput,
+ NumberInput,
+ ReferenceArrayInput,
+ SelectArrayInput,
+} from "react-admin";
+
+import { OrderTitle } from "../order/OrderTitle";
+
+export const ProductCreate = (props: CreateProps): React.ReactElement => {
+ return (
+
+
+
+
+
+ value && value.map((v: any) => ({ id: v }))}
+ format={(value: any) => value && value.map((v: any) => v.id)}
+ >
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/product/ProductEdit.tsx b/admin-ui/src/product/ProductEdit.tsx
new file mode 100644
index 0000000..527be53
--- /dev/null
+++ b/admin-ui/src/product/ProductEdit.tsx
@@ -0,0 +1,33 @@
+import * as React from "react";
+
+import {
+ Edit,
+ SimpleForm,
+ EditProps,
+ TextInput,
+ NumberInput,
+ ReferenceArrayInput,
+ SelectArrayInput,
+} from "react-admin";
+
+import { OrderTitle } from "../order/OrderTitle";
+
+export const ProductEdit = (props: EditProps): React.ReactElement => {
+ return (
+
+
+
+
+
+ value && value.map((v: any) => ({ id: v }))}
+ format={(value: any) => value && value.map((v: any) => v.id)}
+ >
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/product/ProductList.tsx b/admin-ui/src/product/ProductList.tsx
new file mode 100644
index 0000000..e0aa861
--- /dev/null
+++ b/admin-ui/src/product/ProductList.tsx
@@ -0,0 +1,24 @@
+import * as React from "react";
+import { List, Datagrid, ListProps, DateField, TextField } from "react-admin";
+import Pagination from "../Components/Pagination";
+
+export const ProductList = (props: ListProps): React.ReactElement => {
+ return (
+
}
+ >
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/product/ProductShow.tsx b/admin-ui/src/product/ProductShow.tsx
new file mode 100644
index 0000000..3733d51
--- /dev/null
+++ b/admin-ui/src/product/ProductShow.tsx
@@ -0,0 +1,54 @@
+import * as React from "react";
+
+import {
+ Show,
+ SimpleShowLayout,
+ ShowProps,
+ DateField,
+ TextField,
+ ReferenceManyField,
+ Datagrid,
+ ReferenceField,
+} from "react-admin";
+
+import { CUSTOMER_TITLE_FIELD } from "../customer/CustomerTitle";
+import { PRODUCT_TITLE_FIELD } from "./ProductTitle";
+
+export const ProductShow = (props: ShowProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/product/ProductTitle.ts b/admin-ui/src/product/ProductTitle.ts
new file mode 100644
index 0000000..a4c4d98
--- /dev/null
+++ b/admin-ui/src/product/ProductTitle.ts
@@ -0,0 +1,7 @@
+import { Product as TProduct } from "../api/product/Product";
+
+export const PRODUCT_TITLE_FIELD = "name";
+
+export const ProductTitle = (record: TProduct): string => {
+ return record.name || record.id;
+};
diff --git a/admin-ui/src/reportWebVitals.ts b/admin-ui/src/reportWebVitals.ts
new file mode 100644
index 0000000..821a6cd
--- /dev/null
+++ b/admin-ui/src/reportWebVitals.ts
@@ -0,0 +1,17 @@
+import { ReportHandler } from "web-vitals";
+
+const reportWebVitals = (onPerfEntry?: ReportHandler): void => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ void import("web-vitals").then(
+ ({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ }
+ );
+ }
+};
+
+export default reportWebVitals;
diff --git a/admin-ui/src/setupTests.ts b/admin-ui/src/setupTests.ts
new file mode 100644
index 0000000..1dd407a
--- /dev/null
+++ b/admin-ui/src/setupTests.ts
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import "@testing-library/jest-dom";
diff --git a/admin-ui/src/theme/theme.ts b/admin-ui/src/theme/theme.ts
new file mode 100644
index 0000000..56a1153
--- /dev/null
+++ b/admin-ui/src/theme/theme.ts
@@ -0,0 +1,33 @@
+import { defaultTheme } from "react-admin";
+import { createTheme, ThemeOptions } from "@material-ui/core/styles";
+import { merge } from "lodash";
+import createPalette from "@material-ui/core/styles/createPalette";
+
+const palette = createPalette(
+ merge({}, defaultTheme.palette, {
+ primary: {
+ main: "#20a4f3",
+ },
+ secondary: {
+ main: "#7950ed",
+ },
+ error: {
+ main: "#e93c51",
+ },
+ warning: {
+ main: "#f6aa50",
+ },
+ info: {
+ main: "#144bc1",
+ },
+ success: {
+ main: "#31c587",
+ },
+ })
+);
+
+const themeOptions: ThemeOptions = {
+ palette,
+};
+
+export const theme = createTheme(merge({}, defaultTheme, themeOptions));
diff --git a/admin-ui/src/types.ts b/admin-ui/src/types.ts
new file mode 100644
index 0000000..45a457d
--- /dev/null
+++ b/admin-ui/src/types.ts
@@ -0,0 +1,13 @@
+import { JsonValue } from "type-fest";
+
+export type Credentials = {
+ username: string;
+ password: string;
+};
+export type LoginMutateResult = {
+ login: {
+ username: string;
+ accessToken: string;
+ };
+};
+export type InputJsonValue = Omit;
diff --git a/admin-ui/src/user/EnumRoles.ts b/admin-ui/src/user/EnumRoles.ts
new file mode 100644
index 0000000..3df7048
--- /dev/null
+++ b/admin-ui/src/user/EnumRoles.ts
@@ -0,0 +1,3 @@
+export enum EnumRoles {
+ User = "user",
+}
diff --git a/admin-ui/src/user/RolesOptions.ts b/admin-ui/src/user/RolesOptions.ts
new file mode 100644
index 0000000..2f12fcf
--- /dev/null
+++ b/admin-ui/src/user/RolesOptions.ts
@@ -0,0 +1,12 @@
+//@ts-ignore
+import { ROLES } from "./roles";
+
+declare interface Role {
+ name: string;
+ displayName: string;
+}
+
+export const ROLES_OPTIONS = ROLES.map((role: Role) => ({
+ value: role.name,
+ label: role.displayName,
+}));
diff --git a/admin-ui/src/user/UserCreate.tsx b/admin-ui/src/user/UserCreate.tsx
new file mode 100644
index 0000000..def7787
--- /dev/null
+++ b/admin-ui/src/user/UserCreate.tsx
@@ -0,0 +1,31 @@
+import * as React from "react";
+
+import {
+ Create,
+ SimpleForm,
+ CreateProps,
+ TextInput,
+ PasswordInput,
+ SelectArrayInput,
+} from "react-admin";
+
+import { ROLES_OPTIONS } from "../user/RolesOptions";
+
+export const UserCreate = (props: CreateProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/user/UserEdit.tsx b/admin-ui/src/user/UserEdit.tsx
new file mode 100644
index 0000000..8565bf9
--- /dev/null
+++ b/admin-ui/src/user/UserEdit.tsx
@@ -0,0 +1,29 @@
+import * as React from "react";
+import {
+ Edit,
+ SimpleForm,
+ EditProps,
+ TextInput,
+ PasswordInput,
+ SelectArrayInput,
+} from "react-admin";
+import { ROLES_OPTIONS } from "../user/RolesOptions";
+
+export const UserEdit = (props: EditProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/user/UserList.tsx b/admin-ui/src/user/UserList.tsx
new file mode 100644
index 0000000..aa820f4
--- /dev/null
+++ b/admin-ui/src/user/UserList.tsx
@@ -0,0 +1,25 @@
+import * as React from "react";
+import { List, Datagrid, ListProps, DateField, TextField } from "react-admin";
+import Pagination from "../Components/Pagination";
+
+export const UserList = (props: ListProps): React.ReactElement => {
+ return (
+
}
+ >
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/user/UserShow.tsx b/admin-ui/src/user/UserShow.tsx
new file mode 100644
index 0000000..dc33a5b
--- /dev/null
+++ b/admin-ui/src/user/UserShow.tsx
@@ -0,0 +1,24 @@
+import * as React from "react";
+import {
+ Show,
+ SimpleShowLayout,
+ ShowProps,
+ DateField,
+ TextField,
+} from "react-admin";
+
+export const UserShow = (props: ShowProps): React.ReactElement => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/admin-ui/src/user/UserTitle.ts b/admin-ui/src/user/UserTitle.ts
new file mode 100644
index 0000000..29c8719
--- /dev/null
+++ b/admin-ui/src/user/UserTitle.ts
@@ -0,0 +1,7 @@
+import { User as TUser } from "../api/user/User";
+
+export const USER_TITLE_FIELD = "firstName";
+
+export const UserTitle = (record: TUser): string => {
+ return record.firstName || record.id;
+};
diff --git a/admin-ui/src/user/roles.ts b/admin-ui/src/user/roles.ts
new file mode 100644
index 0000000..732870a
--- /dev/null
+++ b/admin-ui/src/user/roles.ts
@@ -0,0 +1,6 @@
+export const ROLES = [
+ {
+ name: "user",
+ displayName: "User",
+ },
+];
diff --git a/admin-ui/src/util/BooleanFilter.ts b/admin-ui/src/util/BooleanFilter.ts
new file mode 100644
index 0000000..a142d58
--- /dev/null
+++ b/admin-ui/src/util/BooleanFilter.ts
@@ -0,0 +1,4 @@
+export class BooleanFilter {
+ equals?: boolean;
+ not?: boolean;
+}
diff --git a/admin-ui/src/util/BooleanNullableFilter.ts b/admin-ui/src/util/BooleanNullableFilter.ts
new file mode 100644
index 0000000..b94aefc
--- /dev/null
+++ b/admin-ui/src/util/BooleanNullableFilter.ts
@@ -0,0 +1,4 @@
+export class BooleanNullableFilter {
+ equals?: boolean | null;
+ not?: boolean | null;
+}
diff --git a/admin-ui/src/util/DateTimeFilter.ts b/admin-ui/src/util/DateTimeFilter.ts
new file mode 100644
index 0000000..cd8d213
--- /dev/null
+++ b/admin-ui/src/util/DateTimeFilter.ts
@@ -0,0 +1,10 @@
+export class DateTimeFilter {
+ equals?: Date;
+ not?: Date;
+ in?: Date[];
+ notIn?: Date[];
+ lt?: Date;
+ lte?: Date;
+ gt?: Date;
+ gte?: Date;
+}
diff --git a/admin-ui/src/util/DateTimeNullableFilter.ts b/admin-ui/src/util/DateTimeNullableFilter.ts
new file mode 100644
index 0000000..2f9c7b3
--- /dev/null
+++ b/admin-ui/src/util/DateTimeNullableFilter.ts
@@ -0,0 +1,10 @@
+export class DateTimeNullableFilter {
+ equals?: Date | null;
+ in?: Date[] | null;
+ notIn?: Date[] | null;
+ lt?: Date;
+ lte?: Date;
+ gt?: Date;
+ gte?: Date;
+ not?: Date;
+}
diff --git a/admin-ui/src/util/FloatFilter.ts b/admin-ui/src/util/FloatFilter.ts
new file mode 100644
index 0000000..62aeb14
--- /dev/null
+++ b/admin-ui/src/util/FloatFilter.ts
@@ -0,0 +1,10 @@
+export class FloatFilter {
+ equals?: number;
+ in?: number[];
+ notIn?: number[];
+ lt?: number;
+ lte?: number;
+ gt?: number;
+ gte?: number;
+ not?: number;
+}
diff --git a/admin-ui/src/util/FloatNullableFilter.ts b/admin-ui/src/util/FloatNullableFilter.ts
new file mode 100644
index 0000000..d7bb163
--- /dev/null
+++ b/admin-ui/src/util/FloatNullableFilter.ts
@@ -0,0 +1,10 @@
+export class FloatNullableFilter {
+ equals?: number | null;
+ in?: number[] | null;
+ notIn?: number[] | null;
+ lt?: number;
+ lte?: number;
+ gt?: number;
+ gte?: number;
+ not?: number;
+}
diff --git a/admin-ui/src/util/IntFilter.ts b/admin-ui/src/util/IntFilter.ts
new file mode 100644
index 0000000..3dc0221
--- /dev/null
+++ b/admin-ui/src/util/IntFilter.ts
@@ -0,0 +1,10 @@
+export class IntFilter {
+ equals?: number;
+ in?: number[];
+ notIn?: number[];
+ lt?: number;
+ lte?: number;
+ gt?: number;
+ gte?: number;
+ not?: number;
+}
diff --git a/admin-ui/src/util/IntNullableFilter.ts b/admin-ui/src/util/IntNullableFilter.ts
new file mode 100644
index 0000000..2107cae
--- /dev/null
+++ b/admin-ui/src/util/IntNullableFilter.ts
@@ -0,0 +1,10 @@
+export class IntNullableFilter {
+ equals?: number | null;
+ in?: number[] | null;
+ notIn?: number[] | null;
+ lt?: number;
+ lte?: number;
+ gt?: number;
+ gte?: number;
+ not?: number;
+}
diff --git a/admin-ui/src/util/JsonFilter.ts b/admin-ui/src/util/JsonFilter.ts
new file mode 100644
index 0000000..cc44763
--- /dev/null
+++ b/admin-ui/src/util/JsonFilter.ts
@@ -0,0 +1,5 @@
+import { InputJsonValue } from "../types";
+export class JsonFilter {
+ equals?: InputJsonValue;
+ not?: InputJsonValue;
+}
diff --git a/admin-ui/src/util/JsonNullableFilter.ts b/admin-ui/src/util/JsonNullableFilter.ts
new file mode 100644
index 0000000..e6d1506
--- /dev/null
+++ b/admin-ui/src/util/JsonNullableFilter.ts
@@ -0,0 +1,5 @@
+import { JsonValue } from "type-fest";
+export class JsonNullableFilter {
+ equals?: JsonValue | null;
+ not?: JsonValue | null;
+}
diff --git a/admin-ui/src/util/MetaQueryPayload.ts b/admin-ui/src/util/MetaQueryPayload.ts
new file mode 100644
index 0000000..bc3175b
--- /dev/null
+++ b/admin-ui/src/util/MetaQueryPayload.ts
@@ -0,0 +1,3 @@
+export class MetaQueryPayload {
+ count!: number;
+}
diff --git a/admin-ui/src/util/QueryMode.ts b/admin-ui/src/util/QueryMode.ts
new file mode 100644
index 0000000..8a2164e
--- /dev/null
+++ b/admin-ui/src/util/QueryMode.ts
@@ -0,0 +1,4 @@
+export enum QueryMode {
+ Default = "default",
+ Insensitive = "insensitive",
+}
diff --git a/admin-ui/src/util/SortOrder.ts b/admin-ui/src/util/SortOrder.ts
new file mode 100644
index 0000000..a5bcdb6
--- /dev/null
+++ b/admin-ui/src/util/SortOrder.ts
@@ -0,0 +1,4 @@
+export enum SortOrder {
+ Asc = "asc",
+ Desc = "desc",
+}
diff --git a/admin-ui/src/util/StringFilter.ts b/admin-ui/src/util/StringFilter.ts
new file mode 100644
index 0000000..c2e26c5
--- /dev/null
+++ b/admin-ui/src/util/StringFilter.ts
@@ -0,0 +1,16 @@
+import { QueryMode } from "./QueryMode";
+
+export class StringFilter {
+ equals?: string;
+ in?: string[];
+ notIn?: string[];
+ lt?: string;
+ lte?: string;
+ gt?: string;
+ gte?: string;
+ contains?: string;
+ startsWith?: string;
+ endsWith?: string;
+ mode?: QueryMode;
+ not?: string;
+}
diff --git a/admin-ui/src/util/StringNullableFilter.ts b/admin-ui/src/util/StringNullableFilter.ts
new file mode 100644
index 0000000..e1e37ec
--- /dev/null
+++ b/admin-ui/src/util/StringNullableFilter.ts
@@ -0,0 +1,15 @@
+import { QueryMode } from "./QueryMode";
+export class StringNullableFilter {
+ equals?: string | null;
+ in?: string[] | null;
+ notIn?: string[] | null;
+ lt?: string;
+ lte?: string;
+ gt?: string;
+ gte?: string;
+ contains?: string;
+ startsWith?: string;
+ endsWith?: string;
+ mode?: QueryMode;
+ not?: string;
+}
diff --git a/admin-ui/tsconfig.json b/admin-ui/tsconfig.json
new file mode 100644
index 0000000..31cc780
--- /dev/null
+++ b/admin-ui/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true
+ },
+ "include": ["src"],
+ "exclude": ["./node_modules"]
+}
diff --git a/server/.dockerignore b/server/.dockerignore
new file mode 100644
index 0000000..cb5c30b
--- /dev/null
+++ b/server/.dockerignore
@@ -0,0 +1,8 @@
+.dockerignore
+docker-compose.yml
+Dockerfile
+dist/
+node_modules
+.env
+.gitignore
+.prettierignore
\ No newline at end of file
diff --git a/server/.env b/server/.env
new file mode 100644
index 0000000..74ee5bf
--- /dev/null
+++ b/server/.env
@@ -0,0 +1,9 @@
+BCRYPT_SALT=10
+COMPOSE_PROJECT_NAME=amp_clao76vfw152759701lm262594kc
+JWT_SECRET_KEY=Change_ME!!!
+JWT_EXPIRATION=2d
+PORT=3000
+DB_USER=admin
+DB_PASSWORD=admin
+DB_PORT=5432
+DB_URL=postgres://admin:admin@localhost:5432
\ No newline at end of file
diff --git a/server/.gitignore b/server/.gitignore
new file mode 100644
index 0000000..b65b772
--- /dev/null
+++ b/server/.gitignore
@@ -0,0 +1,6 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+
+/node_modules
+/dist
+.DS_Store
\ No newline at end of file
diff --git a/server/.prettierignore b/server/.prettierignore
new file mode 100644
index 0000000..e48f355
--- /dev/null
+++ b/server/.prettierignore
@@ -0,0 +1,5 @@
+node_modules/
+dist/
+prisma/migrations/
+package-lock.json
+coverage/
\ No newline at end of file
diff --git a/server/Dockerfile b/server/Dockerfile
new file mode 100644
index 0000000..23e10d0
--- /dev/null
+++ b/server/Dockerfile
@@ -0,0 +1,31 @@
+FROM node:16.16 AS base
+
+WORKDIR /app
+
+COPY package.json ./
+
+RUN npm install
+
+COPY prisma/schema.prisma ./prisma/
+
+RUN npm run prisma:generate
+
+COPY . .
+
+RUN npm run build
+
+FROM node:16.16 AS prod
+
+WORKDIR /app
+
+COPY --from=base /app/node_modules/ ./node_modules
+COPY --from=base /app/package.json ./package.json
+COPY --from=base /app/dist ./dist
+COPY --from=base /app/prisma ./prisma
+COPY --from=base /app/scripts ./scripts
+COPY --from=base /app/src ./src
+COPY --from=base /app/tsconfig* ./
+
+EXPOSE 3000
+
+CMD [ "node", "./dist/main"]
diff --git a/server/docker-compose.db.yml b/server/docker-compose.db.yml
new file mode 100644
index 0000000..8d7c358
--- /dev/null
+++ b/server/docker-compose.db.yml
@@ -0,0 +1,13 @@
+version: "3"
+services:
+ db:
+ image: postgres:12
+ ports:
+ - ${DB_PORT}:5432
+ environment:
+ POSTGRES_USER: ${DB_USER}
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
+ volumes:
+ - postgres:/var/lib/postgresql/data
+volumes:
+ postgres: ~
diff --git a/server/docker-compose.yml b/server/docker-compose.yml
new file mode 100644
index 0000000..441ef7e
--- /dev/null
+++ b/server/docker-compose.yml
@@ -0,0 +1,49 @@
+version: "3"
+services:
+ server:
+ build:
+ context: .
+ args:
+ NPM_LOG_LEVEL: notice
+ ports:
+ - ${PORT}:3000
+ environment:
+ BCRYPT_SALT: ${BCRYPT_SALT}
+ JWT_SECRET_KEY: ${JWT_SECRET_KEY}
+ JWT_EXPIRATION: ${JWT_EXPIRATION}
+ DB_URL: postgres://${DB_USER}:${DB_PASSWORD}@db:5433
+ depends_on:
+ - migrate
+ migrate:
+ build:
+ context: .
+ args:
+ NPM_LOG_LEVEL: notice
+ command: npm run db:init
+ working_dir: /app/server
+ environment:
+ BCRYPT_SALT: ${BCRYPT_SALT}
+ DB_URL: postgres://${DB_USER}:${DB_PASSWORD}@db:5432
+ depends_on:
+ db:
+ condition: service_healthy
+ db:
+ image: postgres:12
+ ports:
+ - ${DB_PORT}:5432
+ environment:
+ POSTGRES_USER: ${DB_USER}
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
+ volumes:
+ - postgres:/var/lib/postgresql/data
+ healthcheck:
+ test:
+ - CMD
+ - sh
+ - -c
+ - pg_isready -q -d ${DB_DB_NAME} -U ${DB_USER}
+ timeout: 45s
+ interval: 10s
+ retries: 10
+volumes:
+ postgres: ~
diff --git a/server/nest-cli.json b/server/nest-cli.json
new file mode 100644
index 0000000..fe51713
--- /dev/null
+++ b/server/nest-cli.json
@@ -0,0 +1,6 @@
+{
+ "sourceRoot": "src",
+ "compilerOptions": {
+ "assets": ["swagger"]
+ }
+}
diff --git a/server/package.json b/server/package.json
new file mode 100644
index 0000000..900c8f2
--- /dev/null
+++ b/server/package.json
@@ -0,0 +1,73 @@
+{
+ "name": "@sample-service/server",
+ "private": true,
+ "scripts": {
+ "start": "nest start",
+ "start:watch": "nest start --watch",
+ "start:debug": "nest start --debug --watch",
+ "build": "nest build",
+ "test": "jest",
+ "seed": "ts-node scripts/seed.ts",
+ "db:migrate-save": "prisma migrate dev",
+ "db:migrate-up": "prisma migrate deploy",
+ "db:clean": "ts-node scripts/clean.ts",
+ "db:init": "run-s \"db:migrate-save -- --name 'initial version'\" db:migrate-up seed",
+ "prisma:generate": "prisma generate",
+ "docker:db": "docker-compose -f docker-compose.db.yml up -d",
+ "docker:build": "docker build .",
+ "compose:up": "docker-compose up -d",
+ "compose:down": "docker-compose down --volumes"
+ },
+ "dependencies": {
+ "@nestjs/common": "8.2.3",
+ "@nestjs/config": "1.1.5",
+ "@nestjs/core": "8.2.3",
+ "@nestjs/graphql": "9.1.2",
+ "@nestjs/jwt": "8.0.0",
+ "@nestjs/passport": "8.2.2",
+ "@nestjs/platform-express": "8.2.3",
+ "@nestjs/serve-static": "2.2.2",
+ "@nestjs/swagger": "5.1.5",
+ "@prisma/client": "3.15.2",
+ "apollo-server-express": "3.6.1",
+ "bcrypt": "5.0.1",
+ "class-transformer": "0.5.1",
+ "class-validator": "0.13.2",
+ "graphql": "15.7.2",
+ "graphql-type-json": "0.3.2",
+ "nest-access-control": "2.0.3",
+ "nest-morgan": "1.0.1",
+ "nestjs-prisma": "0.16.0",
+ "npm-run-all": "4.1.5",
+ "passport": "0.6.0",
+ "passport-http": "0.3.0",
+ "passport-jwt": "4.0.0",
+ "reflect-metadata": "0.1.13",
+ "swagger-ui-express": "4.3.0",
+ "ts-node": "9.1.1"
+ },
+ "devDependencies": {
+ "@nestjs/cli": "8.2.5",
+ "@nestjs/testing": "8.2.3",
+ "@types/bcrypt": "5.0.0",
+ "@types/express": "4.17.9",
+ "@types/graphql-type-json": "0.3.2",
+ "@types/jest": "26.0.19",
+ "@types/normalize-path": "3.0.0",
+ "@types/passport-http": "0.3.9",
+ "@types/passport-jwt": "3.0.6",
+ "@types/supertest": "2.0.11",
+ "jest": "27.0.6",
+ "jest-mock-extended": "^2.0.4",
+ "prisma": "3.15.2",
+ "supertest": "4.0.2",
+ "ts-jest": "27.0.3",
+ "type-fest": "0.11.0",
+ "typescript": "4.2.3"
+ },
+ "jest": {
+ "preset": "ts-jest",
+ "testEnvironment": "node",
+ "modulePathIgnorePatterns": ["/dist/"]
+ }
+}
diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma
new file mode 100644
index 0000000..8aa700c
--- /dev/null
+++ b/server/prisma/schema.prisma
@@ -0,0 +1,67 @@
+datasource postgres {
+ provider = "postgresql"
+ url = env("DB_URL")
+}
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+model User {
+ createdAt DateTime @default(now())
+ firstName String?
+ id String @id @default(cuid())
+ lastName String?
+ password String
+ roles Json
+ updatedAt DateTime @updatedAt
+ username String @unique
+}
+
+model Order {
+ createdAt DateTime @default(now())
+ customer Customer? @relation(fields: [customerId], references: [id])
+ customerId String?
+ discount Float?
+ id String @id @default(cuid())
+ product Product? @relation(fields: [productId], references: [id])
+ productId String?
+ quantity Int?
+ totalPrice Int?
+ updatedAt DateTime @updatedAt
+}
+
+model Customer {
+ address Address? @relation(fields: [addressId], references: [id])
+ addressId String?
+ createdAt DateTime @default(now())
+ email String?
+ firstName String?
+ id String @id @default(cuid())
+ lastName String?
+ orders Order[]
+ phone String?
+ updatedAt DateTime @updatedAt
+}
+
+model Address {
+ address_1 String?
+ address_2 String?
+ city String?
+ createdAt DateTime @default(now())
+ customers Customer[]
+ id String @id @default(cuid())
+ state String?
+ updatedAt DateTime @updatedAt
+ zip Int?
+}
+
+model Product {
+ createdAt DateTime @default(now())
+ description String?
+ id String @id @default(cuid())
+ itemPrice Float?
+ name String?
+ orders Order[]
+ updatedAt DateTime @updatedAt
+}
\ No newline at end of file
diff --git a/server/scripts/clean.ts b/server/scripts/clean.ts
new file mode 100644
index 0000000..fd739e2
--- /dev/null
+++ b/server/scripts/clean.ts
@@ -0,0 +1,57 @@
+/**
+ * Clean all the tables and types created by Prisma in the database
+ */
+
+import { PrismaClient } from "@prisma/client";
+
+if (require.main === module) {
+ clean().catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
+}
+
+async function clean() {
+ console.info("Dropping all tables in the database...");
+ const prisma = new PrismaClient();
+ const tables = await getTables(prisma);
+ const types = await getTypes(prisma);
+ await dropTables(prisma, tables);
+ await dropTypes(prisma, types);
+ console.info("Cleaned database successfully");
+ await prisma.$disconnect();
+}
+
+async function dropTables(
+ prisma: PrismaClient,
+ tables: string[]
+): Promise {
+ for (const table of tables) {
+ await prisma.$executeRawUnsafe(`DROP TABLE public."${table}" CASCADE;`);
+ }
+}
+
+async function dropTypes(prisma: PrismaClient, types: string[]) {
+ for (const type of types) {
+ await prisma.$executeRawUnsafe(`DROP TYPE IF EXISTS "${type}" CASCADE;`);
+ }
+}
+
+async function getTables(prisma: PrismaClient): Promise {
+ const results: Array<{
+ tablename: string;
+ }> = await prisma.$queryRaw`SELECT tablename from pg_tables where schemaname = 'public';`;
+ return results.map((result) => result.tablename);
+}
+
+async function getTypes(prisma: PrismaClient): Promise {
+ const results: Array<{
+ typname: string;
+ }> = await prisma.$queryRaw`
+ SELECT t.typname
+ FROM pg_type t
+ JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
+ WHERE n.nspname = 'public';
+ `;
+ return results.map((result) => result.typname);
+}
diff --git a/server/scripts/customSeed.ts b/server/scripts/customSeed.ts
new file mode 100644
index 0000000..6baf5f6
--- /dev/null
+++ b/server/scripts/customSeed.ts
@@ -0,0 +1,17 @@
+import { PrismaClient } from "@prisma/client";
+
+export async function customSeed() {
+ const client = new PrismaClient();
+ const username = "admin";
+
+ //replace this sample code to populate your database
+ //with data that is required for your service to start
+ await client.user.update({
+ where: { username: username },
+ data: {
+ username,
+ },
+ });
+
+ client.$disconnect();
+}
diff --git a/server/scripts/seed.ts b/server/scripts/seed.ts
new file mode 100644
index 0000000..3bb4d3a
--- /dev/null
+++ b/server/scripts/seed.ts
@@ -0,0 +1,44 @@
+import * as dotenv from "dotenv";
+import { PrismaClient } from "@prisma/client";
+import { Salt, parseSalt } from "../src/auth/password.service";
+import { hash } from "bcrypt";
+import { customSeed } from "./customSeed";
+
+if (require.main === module) {
+ dotenv.config();
+
+ const { BCRYPT_SALT } = process.env;
+
+ if (!BCRYPT_SALT) {
+ throw new Error("BCRYPT_SALT environment variable must be defined");
+ }
+
+ const salt = parseSalt(BCRYPT_SALT);
+
+ seed(salt).catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
+}
+
+async function seed(bcryptSalt: Salt) {
+ console.info("Seeding database...");
+
+ const client = new PrismaClient();
+ const data = {
+ username: "admin",
+ password: await hash("admin", bcryptSalt),
+ roles: ["user"],
+ };
+ await client.user.upsert({
+ where: { username: data.username },
+ update: {},
+ create: data,
+ });
+ void client.$disconnect();
+
+ console.info("Seeding database with custom seed...");
+ customSeed();
+
+ console.info("Seeded database successfully");
+}
diff --git a/server/src/address/address.controller.ts b/server/src/address/address.controller.ts
new file mode 100644
index 0000000..feb5ea1
--- /dev/null
+++ b/server/src/address/address.controller.ts
@@ -0,0 +1,17 @@
+import * as common from "@nestjs/common";
+import * as swagger from "@nestjs/swagger";
+import * as nestAccessControl from "nest-access-control";
+import { AddressService } from "./address.service";
+import { AddressControllerBase } from "./base/address.controller.base";
+
+@swagger.ApiTags("addresses")
+@common.Controller("addresses")
+export class AddressController extends AddressControllerBase {
+ constructor(
+ protected readonly service: AddressService,
+ @nestAccessControl.InjectRolesBuilder()
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {
+ super(service, rolesBuilder);
+ }
+}
diff --git a/server/src/address/address.module.ts b/server/src/address/address.module.ts
new file mode 100644
index 0000000..2c5ddfa
--- /dev/null
+++ b/server/src/address/address.module.ts
@@ -0,0 +1,13 @@
+import { Module } from "@nestjs/common";
+import { AddressModuleBase } from "./base/address.module.base";
+import { AddressService } from "./address.service";
+import { AddressController } from "./address.controller";
+import { AddressResolver } from "./address.resolver";
+
+@Module({
+ imports: [AddressModuleBase],
+ controllers: [AddressController],
+ providers: [AddressService, AddressResolver],
+ exports: [AddressService],
+})
+export class AddressModule {}
diff --git a/server/src/address/address.resolver.ts b/server/src/address/address.resolver.ts
new file mode 100644
index 0000000..b42eb41
--- /dev/null
+++ b/server/src/address/address.resolver.ts
@@ -0,0 +1,20 @@
+import * as common from "@nestjs/common";
+import * as graphql from "@nestjs/graphql";
+import * as nestAccessControl from "nest-access-control";
+import { GqlDefaultAuthGuard } from "../auth/gqlDefaultAuth.guard";
+import * as gqlACGuard from "../auth/gqlAC.guard";
+import { AddressResolverBase } from "./base/address.resolver.base";
+import { Address } from "./base/Address";
+import { AddressService } from "./address.service";
+
+@graphql.Resolver(() => Address)
+@common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+export class AddressResolver extends AddressResolverBase {
+ constructor(
+ protected readonly service: AddressService,
+ @nestAccessControl.InjectRolesBuilder()
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {
+ super(service, rolesBuilder);
+ }
+}
diff --git a/server/src/address/address.service.ts b/server/src/address/address.service.ts
new file mode 100644
index 0000000..7e5cb30
--- /dev/null
+++ b/server/src/address/address.service.ts
@@ -0,0 +1,10 @@
+import { Injectable } from "@nestjs/common";
+import { PrismaService } from "nestjs-prisma";
+import { AddressServiceBase } from "./base/address.service.base";
+
+@Injectable()
+export class AddressService extends AddressServiceBase {
+ constructor(protected readonly prisma: PrismaService) {
+ super(prisma);
+ }
+}
diff --git a/server/src/address/base/Address.ts b/server/src/address/base/Address.ts
new file mode 100644
index 0000000..9b34272
--- /dev/null
+++ b/server/src/address/base/Address.ts
@@ -0,0 +1,113 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ObjectType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import {
+ IsString,
+ IsOptional,
+ IsDate,
+ ValidateNested,
+ IsInt,
+} from "class-validator";
+import { Type } from "class-transformer";
+import { Customer } from "../../customer/base/Customer";
+@ObjectType()
+class Address {
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ address_1!: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ address_2!: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ city!: string | null;
+
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ createdAt!: Date;
+
+ @ApiProperty({
+ required: false,
+ type: () => [Customer],
+ })
+ @ValidateNested()
+ @Type(() => Customer)
+ @IsOptional()
+ customers?: Array;
+
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ state!: string | null;
+
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ updatedAt!: Date;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsInt()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ zip!: number | null;
+}
+export { Address };
diff --git a/server/src/address/base/AddressCreateInput.ts b/server/src/address/base/AddressCreateInput.ts
new file mode 100644
index 0000000..0cb5976
--- /dev/null
+++ b/server/src/address/base/AddressCreateInput.ts
@@ -0,0 +1,86 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsString, IsOptional, ValidateNested, IsInt } from "class-validator";
+import { CustomerCreateNestedManyWithoutAddressesInput } from "./CustomerCreateNestedManyWithoutAddressesInput";
+import { Type } from "class-transformer";
+@InputType()
+class AddressCreateInput {
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ address_1?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ address_2?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ city?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => CustomerCreateNestedManyWithoutAddressesInput,
+ })
+ @ValidateNested()
+ @Type(() => CustomerCreateNestedManyWithoutAddressesInput)
+ @IsOptional()
+ @Field(() => CustomerCreateNestedManyWithoutAddressesInput, {
+ nullable: true,
+ })
+ customers?: CustomerCreateNestedManyWithoutAddressesInput;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ state?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsInt()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ zip?: number | null;
+}
+export { AddressCreateInput };
diff --git a/server/src/address/base/AddressFindManyArgs.ts b/server/src/address/base/AddressFindManyArgs.ts
new file mode 100644
index 0000000..da3e409
--- /dev/null
+++ b/server/src/address/base/AddressFindManyArgs.ts
@@ -0,0 +1,53 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { AddressWhereInput } from "./AddressWhereInput";
+import { Type } from "class-transformer";
+import { AddressOrderByInput } from "./AddressOrderByInput";
+
+@ArgsType()
+class AddressFindManyArgs {
+ @ApiProperty({
+ required: false,
+ type: () => AddressWhereInput,
+ })
+ @Field(() => AddressWhereInput, { nullable: true })
+ @Type(() => AddressWhereInput)
+ where?: AddressWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: [AddressOrderByInput],
+ })
+ @Field(() => [AddressOrderByInput], { nullable: true })
+ @Type(() => AddressOrderByInput)
+ orderBy?: Array;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ skip?: number;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ take?: number;
+}
+
+export { AddressFindManyArgs };
diff --git a/server/src/address/base/AddressFindUniqueArgs.ts b/server/src/address/base/AddressFindUniqueArgs.ts
new file mode 100644
index 0000000..2f8fc1e
--- /dev/null
+++ b/server/src/address/base/AddressFindUniqueArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { AddressWhereUniqueInput } from "./AddressWhereUniqueInput";
+
+@ArgsType()
+class AddressFindUniqueArgs {
+ @Field(() => AddressWhereUniqueInput, { nullable: false })
+ where!: AddressWhereUniqueInput;
+}
+
+export { AddressFindUniqueArgs };
diff --git a/server/src/address/base/AddressListRelationFilter.ts b/server/src/address/base/AddressListRelationFilter.ts
new file mode 100644
index 0000000..0854eb7
--- /dev/null
+++ b/server/src/address/base/AddressListRelationFilter.ts
@@ -0,0 +1,56 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { AddressWhereInput } from "./AddressWhereInput";
+import { ValidateNested, IsOptional } from "class-validator";
+import { Type } from "class-transformer";
+
+@InputType()
+class AddressListRelationFilter {
+ @ApiProperty({
+ required: false,
+ type: () => AddressWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => AddressWhereInput)
+ @IsOptional()
+ @Field(() => AddressWhereInput, {
+ nullable: true,
+ })
+ every?: AddressWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => AddressWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => AddressWhereInput)
+ @IsOptional()
+ @Field(() => AddressWhereInput, {
+ nullable: true,
+ })
+ some?: AddressWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => AddressWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => AddressWhereInput)
+ @IsOptional()
+ @Field(() => AddressWhereInput, {
+ nullable: true,
+ })
+ none?: AddressWhereInput;
+}
+export { AddressListRelationFilter };
diff --git a/server/src/address/base/AddressOrderByInput.ts b/server/src/address/base/AddressOrderByInput.ts
new file mode 100644
index 0000000..76aa241
--- /dev/null
+++ b/server/src/address/base/AddressOrderByInput.ts
@@ -0,0 +1,94 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { SortOrder } from "../../util/SortOrder";
+
+@InputType({
+ isAbstract: true,
+ description: undefined,
+})
+class AddressOrderByInput {
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ address_1?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ address_2?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ city?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ createdAt?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ id?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ state?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ updatedAt?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ zip?: SortOrder;
+}
+
+export { AddressOrderByInput };
diff --git a/server/src/address/base/AddressUpdateInput.ts b/server/src/address/base/AddressUpdateInput.ts
new file mode 100644
index 0000000..7dae894
--- /dev/null
+++ b/server/src/address/base/AddressUpdateInput.ts
@@ -0,0 +1,86 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsString, IsOptional, ValidateNested, IsInt } from "class-validator";
+import { CustomerUpdateManyWithoutAddressesInput } from "./CustomerUpdateManyWithoutAddressesInput";
+import { Type } from "class-transformer";
+@InputType()
+class AddressUpdateInput {
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ address_1?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ address_2?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ city?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => CustomerUpdateManyWithoutAddressesInput,
+ })
+ @ValidateNested()
+ @Type(() => CustomerUpdateManyWithoutAddressesInput)
+ @IsOptional()
+ @Field(() => CustomerUpdateManyWithoutAddressesInput, {
+ nullable: true,
+ })
+ customers?: CustomerUpdateManyWithoutAddressesInput;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ state?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsInt()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ zip?: number | null;
+}
+export { AddressUpdateInput };
diff --git a/server/src/address/base/AddressWhereInput.ts b/server/src/address/base/AddressWhereInput.ts
new file mode 100644
index 0000000..cadf12e
--- /dev/null
+++ b/server/src/address/base/AddressWhereInput.ts
@@ -0,0 +1,100 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { StringNullableFilter } from "../../util/StringNullableFilter";
+import { Type } from "class-transformer";
+import { IsOptional, ValidateNested } from "class-validator";
+import { CustomerListRelationFilter } from "../../customer/base/CustomerListRelationFilter";
+import { StringFilter } from "../../util/StringFilter";
+import { IntNullableFilter } from "../../util/IntNullableFilter";
+@InputType()
+class AddressWhereInput {
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ address_1?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ address_2?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ city?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: () => CustomerListRelationFilter,
+ })
+ @ValidateNested()
+ @Type(() => CustomerListRelationFilter)
+ @IsOptional()
+ @Field(() => CustomerListRelationFilter, {
+ nullable: true,
+ })
+ customers?: CustomerListRelationFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringFilter,
+ })
+ @Type(() => StringFilter)
+ @IsOptional()
+ @Field(() => StringFilter, {
+ nullable: true,
+ })
+ id?: StringFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ state?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: IntNullableFilter,
+ })
+ @Type(() => IntNullableFilter)
+ @IsOptional()
+ @Field(() => IntNullableFilter, {
+ nullable: true,
+ })
+ zip?: IntNullableFilter;
+}
+export { AddressWhereInput };
diff --git a/server/src/address/base/AddressWhereUniqueInput.ts b/server/src/address/base/AddressWhereUniqueInput.ts
new file mode 100644
index 0000000..7f391c7
--- /dev/null
+++ b/server/src/address/base/AddressWhereUniqueInput.ts
@@ -0,0 +1,25 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsString } from "class-validator";
+@InputType()
+class AddressWhereUniqueInput {
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+}
+export { AddressWhereUniqueInput };
diff --git a/server/src/address/base/CreateAddressArgs.ts b/server/src/address/base/CreateAddressArgs.ts
new file mode 100644
index 0000000..22acaa5
--- /dev/null
+++ b/server/src/address/base/CreateAddressArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { AddressCreateInput } from "./AddressCreateInput";
+
+@ArgsType()
+class CreateAddressArgs {
+ @Field(() => AddressCreateInput, { nullable: false })
+ data!: AddressCreateInput;
+}
+
+export { CreateAddressArgs };
diff --git a/server/src/address/base/CustomerCreateNestedManyWithoutAddressesInput.ts b/server/src/address/base/CustomerCreateNestedManyWithoutAddressesInput.ts
new file mode 100644
index 0000000..f3b2e83
--- /dev/null
+++ b/server/src/address/base/CustomerCreateNestedManyWithoutAddressesInput.ts
@@ -0,0 +1,26 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { CustomerWhereUniqueInput } from "../../customer/base/CustomerWhereUniqueInput";
+import { ApiProperty } from "@nestjs/swagger";
+@InputType()
+class CustomerCreateNestedManyWithoutAddressesInput {
+ @Field(() => [CustomerWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [CustomerWhereUniqueInput],
+ })
+ connect?: Array;
+}
+export { CustomerCreateNestedManyWithoutAddressesInput };
diff --git a/server/src/address/base/CustomerUpdateManyWithoutAddressesInput.ts b/server/src/address/base/CustomerUpdateManyWithoutAddressesInput.ts
new file mode 100644
index 0000000..c2daff4
--- /dev/null
+++ b/server/src/address/base/CustomerUpdateManyWithoutAddressesInput.ts
@@ -0,0 +1,44 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { CustomerWhereUniqueInput } from "../../customer/base/CustomerWhereUniqueInput";
+import { ApiProperty } from "@nestjs/swagger";
+@InputType()
+class CustomerUpdateManyWithoutAddressesInput {
+ @Field(() => [CustomerWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [CustomerWhereUniqueInput],
+ })
+ connect?: Array;
+
+ @Field(() => [CustomerWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [CustomerWhereUniqueInput],
+ })
+ disconnect?: Array;
+
+ @Field(() => [CustomerWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [CustomerWhereUniqueInput],
+ })
+ set?: Array;
+}
+export { CustomerUpdateManyWithoutAddressesInput };
diff --git a/server/src/address/base/DeleteAddressArgs.ts b/server/src/address/base/DeleteAddressArgs.ts
new file mode 100644
index 0000000..1143fa9
--- /dev/null
+++ b/server/src/address/base/DeleteAddressArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { AddressWhereUniqueInput } from "./AddressWhereUniqueInput";
+
+@ArgsType()
+class DeleteAddressArgs {
+ @Field(() => AddressWhereUniqueInput, { nullable: false })
+ where!: AddressWhereUniqueInput;
+}
+
+export { DeleteAddressArgs };
diff --git a/server/src/address/base/UpdateAddressArgs.ts b/server/src/address/base/UpdateAddressArgs.ts
new file mode 100644
index 0000000..28baf9b
--- /dev/null
+++ b/server/src/address/base/UpdateAddressArgs.ts
@@ -0,0 +1,24 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { AddressWhereUniqueInput } from "./AddressWhereUniqueInput";
+import { AddressUpdateInput } from "./AddressUpdateInput";
+
+@ArgsType()
+class UpdateAddressArgs {
+ @Field(() => AddressWhereUniqueInput, { nullable: false })
+ where!: AddressWhereUniqueInput;
+ @Field(() => AddressUpdateInput, { nullable: false })
+ data!: AddressUpdateInput;
+}
+
+export { UpdateAddressArgs };
diff --git a/server/src/address/base/address.controller.base.spec.ts b/server/src/address/base/address.controller.base.spec.ts
new file mode 100644
index 0000000..a818c70
--- /dev/null
+++ b/server/src/address/base/address.controller.base.spec.ts
@@ -0,0 +1,189 @@
+import { Test } from "@nestjs/testing";
+import {
+ INestApplication,
+ HttpStatus,
+ ExecutionContext,
+ CallHandler,
+} from "@nestjs/common";
+import request from "supertest";
+import { MorganModule } from "nest-morgan";
+import { ACGuard } from "nest-access-control";
+import { DefaultAuthGuard } from "../../auth/defaultAuth.guard";
+import { ACLModule } from "../../auth/acl.module";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { map } from "rxjs";
+import { AddressController } from "../address.controller";
+import { AddressService } from "../address.service";
+
+const nonExistingId = "nonExistingId";
+const existingId = "existingId";
+const CREATE_INPUT = {
+ address_1: "exampleAddress_1",
+ address_2: "exampleAddress_2",
+ city: "exampleCity",
+ createdAt: new Date(),
+ id: "exampleId",
+ state: "exampleState",
+ updatedAt: new Date(),
+ zip: 42,
+};
+const CREATE_RESULT = {
+ address_1: "exampleAddress_1",
+ address_2: "exampleAddress_2",
+ city: "exampleCity",
+ createdAt: new Date(),
+ id: "exampleId",
+ state: "exampleState",
+ updatedAt: new Date(),
+ zip: 42,
+};
+const FIND_MANY_RESULT = [
+ {
+ address_1: "exampleAddress_1",
+ address_2: "exampleAddress_2",
+ city: "exampleCity",
+ createdAt: new Date(),
+ id: "exampleId",
+ state: "exampleState",
+ updatedAt: new Date(),
+ zip: 42,
+ },
+];
+const FIND_ONE_RESULT = {
+ address_1: "exampleAddress_1",
+ address_2: "exampleAddress_2",
+ city: "exampleCity",
+ createdAt: new Date(),
+ id: "exampleId",
+ state: "exampleState",
+ updatedAt: new Date(),
+ zip: 42,
+};
+
+const service = {
+ create() {
+ return CREATE_RESULT;
+ },
+ findMany: () => FIND_MANY_RESULT,
+ findOne: ({ where }: { where: { id: string } }) => {
+ switch (where.id) {
+ case existingId:
+ return FIND_ONE_RESULT;
+ case nonExistingId:
+ return null;
+ }
+ },
+};
+
+const basicAuthGuard = {
+ canActivate: (context: ExecutionContext) => {
+ const argumentHost = context.switchToHttp();
+ const request = argumentHost.getRequest();
+ request.user = {
+ roles: ["user"],
+ };
+ return true;
+ },
+};
+
+const acGuard = {
+ canActivate: () => {
+ return true;
+ },
+};
+
+const aclFilterResponseInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle().pipe(
+ map((data) => {
+ return data;
+ })
+ );
+ },
+};
+const aclValidateRequestInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle();
+ },
+};
+
+describe("Address", () => {
+ let app: INestApplication;
+
+ beforeAll(async () => {
+ const moduleRef = await Test.createTestingModule({
+ providers: [
+ {
+ provide: AddressService,
+ useValue: service,
+ },
+ ],
+ controllers: [AddressController],
+ imports: [MorganModule.forRoot(), ACLModule],
+ })
+ .overrideGuard(DefaultAuthGuard)
+ .useValue(basicAuthGuard)
+ .overrideGuard(ACGuard)
+ .useValue(acGuard)
+ .overrideInterceptor(AclFilterResponseInterceptor)
+ .useValue(aclFilterResponseInterceptor)
+ .overrideInterceptor(AclValidateRequestInterceptor)
+ .useValue(aclValidateRequestInterceptor)
+ .compile();
+
+ app = moduleRef.createNestApplication();
+ await app.init();
+ });
+
+ test("POST /addresses", async () => {
+ await request(app.getHttpServer())
+ .post("/addresses")
+ .send(CREATE_INPUT)
+ .expect(HttpStatus.CREATED)
+ .expect({
+ ...CREATE_RESULT,
+ createdAt: CREATE_RESULT.createdAt.toISOString(),
+ updatedAt: CREATE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ test("GET /addresses", async () => {
+ await request(app.getHttpServer())
+ .get("/addresses")
+ .expect(HttpStatus.OK)
+ .expect([
+ {
+ ...FIND_MANY_RESULT[0],
+ createdAt: FIND_MANY_RESULT[0].createdAt.toISOString(),
+ updatedAt: FIND_MANY_RESULT[0].updatedAt.toISOString(),
+ },
+ ]);
+ });
+
+ test("GET /addresses/:id non existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/addresses"}/${nonExistingId}`)
+ .expect(HttpStatus.NOT_FOUND)
+ .expect({
+ statusCode: HttpStatus.NOT_FOUND,
+ message: `No resource was found for {"${"id"}":"${nonExistingId}"}`,
+ error: "Not Found",
+ });
+ });
+
+ test("GET /addresses/:id existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/addresses"}/${existingId}`)
+ .expect(HttpStatus.OK)
+ .expect({
+ ...FIND_ONE_RESULT,
+ createdAt: FIND_ONE_RESULT.createdAt.toISOString(),
+ updatedAt: FIND_ONE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ afterAll(async () => {
+ await app.close();
+ });
+});
diff --git a/server/src/address/base/address.controller.base.ts b/server/src/address/base/address.controller.base.ts
new file mode 100644
index 0000000..8f8a9c8
--- /dev/null
+++ b/server/src/address/base/address.controller.base.ts
@@ -0,0 +1,306 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as swagger from "@nestjs/swagger";
+import * as nestAccessControl from "nest-access-control";
+import * as defaultAuthGuard from "../../auth/defaultAuth.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import * as errors from "../../errors";
+import { Request } from "express";
+import { plainToClass } from "class-transformer";
+import { ApiNestedQuery } from "../../decorators/api-nested-query.decorator";
+import { AddressService } from "../address.service";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AddressCreateInput } from "./AddressCreateInput";
+import { AddressWhereInput } from "./AddressWhereInput";
+import { AddressWhereUniqueInput } from "./AddressWhereUniqueInput";
+import { AddressFindManyArgs } from "./AddressFindManyArgs";
+import { AddressUpdateInput } from "./AddressUpdateInput";
+import { Address } from "./Address";
+import { CustomerFindManyArgs } from "../../customer/base/CustomerFindManyArgs";
+import { Customer } from "../../customer/base/Customer";
+import { CustomerWhereUniqueInput } from "../../customer/base/CustomerWhereUniqueInput";
+@swagger.ApiBearerAuth()
+@common.UseGuards(defaultAuthGuard.DefaultAuthGuard, nestAccessControl.ACGuard)
+export class AddressControllerBase {
+ constructor(
+ protected readonly service: AddressService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "create",
+ possession: "any",
+ })
+ @common.Post()
+ @swagger.ApiCreatedResponse({ type: Address })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async create(@common.Body() data: AddressCreateInput): Promise {
+ return await this.service.create({
+ data: data,
+ select: {
+ address_1: true,
+ address_2: true,
+ city: true,
+ createdAt: true,
+ id: true,
+ state: true,
+ updatedAt: true,
+ zip: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "read",
+ possession: "any",
+ })
+ @common.Get()
+ @swagger.ApiOkResponse({ type: [Address] })
+ @swagger.ApiForbiddenResponse()
+ @ApiNestedQuery(AddressFindManyArgs)
+ async findMany(@common.Req() request: Request): Promise {
+ const args = plainToClass(AddressFindManyArgs, request.query);
+ return this.service.findMany({
+ ...args,
+ select: {
+ address_1: true,
+ address_2: true,
+ city: true,
+ createdAt: true,
+ id: true,
+ state: true,
+ updatedAt: true,
+ zip: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "read",
+ possession: "own",
+ })
+ @common.Get("/:id")
+ @swagger.ApiOkResponse({ type: Address })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async findOne(
+ @common.Param() params: AddressWhereUniqueInput
+ ): Promise {
+ const result = await this.service.findOne({
+ where: params,
+ select: {
+ address_1: true,
+ address_2: true,
+ city: true,
+ createdAt: true,
+ id: true,
+ state: true,
+ updatedAt: true,
+ zip: true,
+ },
+ });
+ if (result === null) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "update",
+ possession: "any",
+ })
+ @common.Patch("/:id")
+ @swagger.ApiOkResponse({ type: Address })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async update(
+ @common.Param() params: AddressWhereUniqueInput,
+ @common.Body() data: AddressUpdateInput
+ ): Promise {
+ try {
+ return await this.service.update({
+ where: params,
+ data: data,
+ select: {
+ address_1: true,
+ address_2: true,
+ city: true,
+ createdAt: true,
+ id: true,
+ state: true,
+ updatedAt: true,
+ zip: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "delete",
+ possession: "any",
+ })
+ @common.Delete("/:id")
+ @swagger.ApiOkResponse({ type: Address })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async delete(
+ @common.Param() params: AddressWhereUniqueInput
+ ): Promise {
+ try {
+ return await this.service.delete({
+ where: params,
+ select: {
+ address_1: true,
+ address_2: true,
+ city: true,
+ createdAt: true,
+ id: true,
+ state: true,
+ updatedAt: true,
+ zip: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "read",
+ possession: "any",
+ })
+ @common.Get("/:id/customers")
+ @ApiNestedQuery(CustomerFindManyArgs)
+ async findManyCustomers(
+ @common.Req() request: Request,
+ @common.Param() params: AddressWhereUniqueInput
+ ): Promise {
+ const query = plainToClass(CustomerFindManyArgs, request.query);
+ const results = await this.service.findCustomers(params.id, {
+ ...query,
+ select: {
+ address: {
+ select: {
+ id: true,
+ },
+ },
+
+ createdAt: true,
+ email: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ phone: true,
+ updatedAt: true,
+ },
+ });
+ if (results === null) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ return results;
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "update",
+ possession: "any",
+ })
+ @common.Post("/:id/customers")
+ async connectCustomers(
+ @common.Param() params: AddressWhereUniqueInput,
+ @common.Body() body: CustomerWhereUniqueInput[]
+ ): Promise {
+ const data = {
+ customers: {
+ connect: body,
+ },
+ };
+ await this.service.update({
+ where: params,
+ data,
+ select: { id: true },
+ });
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "update",
+ possession: "any",
+ })
+ @common.Patch("/:id/customers")
+ async updateCustomers(
+ @common.Param() params: AddressWhereUniqueInput,
+ @common.Body() body: CustomerWhereUniqueInput[]
+ ): Promise {
+ const data = {
+ customers: {
+ set: body,
+ },
+ };
+ await this.service.update({
+ where: params,
+ data,
+ select: { id: true },
+ });
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "update",
+ possession: "any",
+ })
+ @common.Delete("/:id/customers")
+ async disconnectCustomers(
+ @common.Param() params: AddressWhereUniqueInput,
+ @common.Body() body: CustomerWhereUniqueInput[]
+ ): Promise {
+ const data = {
+ customers: {
+ disconnect: body,
+ },
+ };
+ await this.service.update({
+ where: params,
+ data,
+ select: { id: true },
+ });
+ }
+}
diff --git a/server/src/address/base/address.module.base.ts b/server/src/address/base/address.module.base.ts
new file mode 100644
index 0000000..c6e5d65
--- /dev/null
+++ b/server/src/address/base/address.module.base.ts
@@ -0,0 +1,28 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { Module, forwardRef } from "@nestjs/common";
+import { MorganModule } from "nest-morgan";
+import { PrismaModule } from "nestjs-prisma";
+import { ACLModule } from "../../auth/acl.module";
+import { AuthModule } from "../../auth/auth.module";
+
+@Module({
+ imports: [
+ ACLModule,
+ forwardRef(() => AuthModule),
+ MorganModule,
+ PrismaModule,
+ ],
+
+ exports: [ACLModule, AuthModule, MorganModule, PrismaModule],
+})
+export class AddressModuleBase {}
diff --git a/server/src/address/base/address.resolver.base.ts b/server/src/address/base/address.resolver.base.ts
new file mode 100644
index 0000000..0e576e3
--- /dev/null
+++ b/server/src/address/base/address.resolver.base.ts
@@ -0,0 +1,170 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as graphql from "@nestjs/graphql";
+import * as apollo from "apollo-server-express";
+import * as nestAccessControl from "nest-access-control";
+import { GqlDefaultAuthGuard } from "../../auth/gqlDefaultAuth.guard";
+import * as gqlACGuard from "../../auth/gqlAC.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import { MetaQueryPayload } from "../../util/MetaQueryPayload";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { CreateAddressArgs } from "./CreateAddressArgs";
+import { UpdateAddressArgs } from "./UpdateAddressArgs";
+import { DeleteAddressArgs } from "./DeleteAddressArgs";
+import { AddressFindManyArgs } from "./AddressFindManyArgs";
+import { AddressFindUniqueArgs } from "./AddressFindUniqueArgs";
+import { Address } from "./Address";
+import { CustomerFindManyArgs } from "../../customer/base/CustomerFindManyArgs";
+import { Customer } from "../../customer/base/Customer";
+import { AddressService } from "../address.service";
+
+@graphql.Resolver(() => Address)
+@common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+export class AddressResolverBase {
+ constructor(
+ protected readonly service: AddressService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @graphql.Query(() => MetaQueryPayload)
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "read",
+ possession: "any",
+ })
+ async _addressesMeta(
+ @graphql.Args() args: AddressFindManyArgs
+ ): Promise {
+ const results = await this.service.count({
+ ...args,
+ skip: undefined,
+ take: undefined,
+ });
+ return {
+ count: results,
+ };
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => [Address])
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "read",
+ possession: "any",
+ })
+ async addresses(
+ @graphql.Args() args: AddressFindManyArgs
+ ): Promise {
+ return this.service.findMany(args);
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => Address, { nullable: true })
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "read",
+ possession: "own",
+ })
+ async address(
+ @graphql.Args() args: AddressFindUniqueArgs
+ ): Promise {
+ const result = await this.service.findOne(args);
+ if (result === null) {
+ return null;
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => Address)
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "create",
+ possession: "any",
+ })
+ async createAddress(
+ @graphql.Args() args: CreateAddressArgs
+ ): Promise {
+ return await this.service.create({
+ ...args,
+ data: args.data,
+ });
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => Address)
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "update",
+ possession: "any",
+ })
+ async updateAddress(
+ @graphql.Args() args: UpdateAddressArgs
+ ): Promise {
+ try {
+ return await this.service.update({
+ ...args,
+ data: args.data,
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @graphql.Mutation(() => Address)
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "delete",
+ possession: "any",
+ })
+ async deleteAddress(
+ @graphql.Args() args: DeleteAddressArgs
+ ): Promise {
+ try {
+ return await this.service.delete(args);
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.ResolveField(() => [Customer])
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "read",
+ possession: "any",
+ })
+ async customers(
+ @graphql.Parent() parent: Address,
+ @graphql.Args() args: CustomerFindManyArgs
+ ): Promise {
+ const results = await this.service.findCustomers(parent.id, args);
+
+ if (!results) {
+ return [];
+ }
+
+ return results;
+ }
+}
diff --git a/server/src/address/base/address.service.base.ts b/server/src/address/base/address.service.base.ts
new file mode 100644
index 0000000..0490ccf
--- /dev/null
+++ b/server/src/address/base/address.service.base.ts
@@ -0,0 +1,60 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { PrismaService } from "nestjs-prisma";
+import { Prisma, Address, Customer } from "@prisma/client";
+
+export class AddressServiceBase {
+ constructor(protected readonly prisma: PrismaService) {}
+
+ async count(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.address.count(args);
+ }
+
+ async findMany(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.address.findMany(args);
+ }
+ async findOne(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.address.findUnique(args);
+ }
+ async create(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.address.create(args);
+ }
+ async update(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.address.update(args);
+ }
+ async delete(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.address.delete(args);
+ }
+
+ async findCustomers(
+ parentId: string,
+ args: Prisma.CustomerFindManyArgs
+ ): Promise {
+ return this.prisma.address
+ .findUnique({
+ where: { id: parentId },
+ })
+ .customers(args);
+ }
+}
diff --git a/server/src/app.module.ts b/server/src/app.module.ts
new file mode 100644
index 0000000..888c491
--- /dev/null
+++ b/server/src/app.module.ts
@@ -0,0 +1,58 @@
+import { Module, Scope } from "@nestjs/common";
+import { APP_INTERCEPTOR } from "@nestjs/core";
+import { MorganInterceptor, MorganModule } from "nest-morgan";
+import { UserModule } from "./user/user.module";
+import { OrderModule } from "./order/order.module";
+import { CustomerModule } from "./customer/customer.module";
+import { AddressModule } from "./address/address.module";
+import { ProductModule } from "./product/product.module";
+import { ACLModule } from "./auth/acl.module";
+import { AuthModule } from "./auth/auth.module";
+import { HealthModule } from "./health/health.module";
+import { SecretsManagerModule } from "./providers/secrets/secretsManager.module";
+import { ConfigModule, ConfigService } from "@nestjs/config";
+import { ServeStaticModule } from "@nestjs/serve-static";
+import { ServeStaticOptionsService } from "./serveStaticOptions.service";
+import { GraphQLModule } from "@nestjs/graphql";
+
+@Module({
+ controllers: [],
+ imports: [
+ UserModule,
+ OrderModule,
+ CustomerModule,
+ AddressModule,
+ ProductModule,
+ ACLModule,
+ AuthModule,
+ HealthModule,
+ SecretsManagerModule,
+ MorganModule,
+ ConfigModule.forRoot({ isGlobal: true }),
+ ServeStaticModule.forRootAsync({
+ useClass: ServeStaticOptionsService,
+ }),
+ GraphQLModule.forRootAsync({
+ useFactory: (configService) => {
+ const playground = configService.get("GRAPHQL_PLAYGROUND");
+ const introspection = configService.get("GRAPHQL_INTROSPECTION");
+ return {
+ autoSchemaFile: "schema.graphql",
+ sortSchema: true,
+ playground,
+ introspection: playground || introspection,
+ };
+ },
+ inject: [ConfigService],
+ imports: [ConfigModule],
+ }),
+ ],
+ providers: [
+ {
+ provide: APP_INTERCEPTOR,
+ scope: Scope.REQUEST,
+ useClass: MorganInterceptor("combined"),
+ },
+ ],
+})
+export class AppModule {}
diff --git a/server/src/auth/Credentials.ts b/server/src/auth/Credentials.ts
new file mode 100644
index 0000000..9ac6798
--- /dev/null
+++ b/server/src/auth/Credentials.ts
@@ -0,0 +1,21 @@
+import { ApiProperty } from "@nestjs/swagger";
+import { InputType, Field } from "@nestjs/graphql";
+import { IsString } from "class-validator";
+
+@InputType()
+export class Credentials {
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String, { nullable: false })
+ username!: string;
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String, { nullable: false })
+ password!: string;
+}
diff --git a/server/src/auth/IAuthStrategy.ts b/server/src/auth/IAuthStrategy.ts
new file mode 100644
index 0000000..5db10cf
--- /dev/null
+++ b/server/src/auth/IAuthStrategy.ts
@@ -0,0 +1,5 @@
+import { UserInfo } from "./UserInfo";
+
+export interface IAuthStrategy {
+ validate: (...any: any) => Promise;
+}
diff --git a/server/src/auth/ITokenService.ts b/server/src/auth/ITokenService.ts
new file mode 100644
index 0000000..7983189
--- /dev/null
+++ b/server/src/auth/ITokenService.ts
@@ -0,0 +1,9 @@
+export interface ITokenPayload {
+ id: string;
+ username: string;
+ password: string;
+}
+
+export interface ITokenService {
+ createToken: ({ id, username, password }: ITokenPayload) => Promise;
+}
diff --git a/server/src/auth/LoginArgs.ts b/server/src/auth/LoginArgs.ts
new file mode 100644
index 0000000..1aa4803
--- /dev/null
+++ b/server/src/auth/LoginArgs.ts
@@ -0,0 +1,8 @@
+import { ArgsType, Field } from "@nestjs/graphql";
+import { Credentials } from "./Credentials";
+
+@ArgsType()
+export class LoginArgs {
+ @Field(() => Credentials, { nullable: false })
+ credentials!: Credentials;
+}
diff --git a/server/src/auth/UserInfo.ts b/server/src/auth/UserInfo.ts
new file mode 100644
index 0000000..e4755a5
--- /dev/null
+++ b/server/src/auth/UserInfo.ts
@@ -0,0 +1,16 @@
+import { Field, ObjectType } from "@nestjs/graphql";
+// @ts-ignore
+// eslint-disable-next-line
+import { User } from "../user/user";
+
+@ObjectType()
+export class UserInfo implements Partial {
+ @Field(() => String)
+ id!: string;
+ @Field(() => String)
+ username!: string;
+ @Field(() => [String])
+ roles!: string[];
+ @Field(() => String, { nullable: true })
+ accessToken?: string;
+}
diff --git a/server/src/auth/abac.util.ts b/server/src/auth/abac.util.ts
new file mode 100644
index 0000000..7047513
--- /dev/null
+++ b/server/src/auth/abac.util.ts
@@ -0,0 +1,14 @@
+import { Permission } from "accesscontrol";
+
+/**
+ * @returns attributes not allowed to appear on given data according to given
+ * attributeMatchers
+ */
+export function getInvalidAttributes(
+ permission: Permission,
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ data: Object
+): string[] {
+ const filteredData = permission.filter(data);
+ return Object.keys(data).filter((key) => !(key in filteredData));
+}
diff --git a/server/src/auth/acl.module.ts b/server/src/auth/acl.module.ts
new file mode 100644
index 0000000..eb90005
--- /dev/null
+++ b/server/src/auth/acl.module.ts
@@ -0,0 +1,7 @@
+import { AccessControlModule, RolesBuilder } from "nest-access-control";
+// @ts-ignore
+// eslint-disable-next-line import/no-unresolved
+import grants from "../grants.json";
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export const ACLModule = AccessControlModule.forRoles(new RolesBuilder(grants));
diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts
new file mode 100644
index 0000000..3eb5676
--- /dev/null
+++ b/server/src/auth/auth.controller.ts
@@ -0,0 +1,15 @@
+import { Body, Controller, Post } from "@nestjs/common";
+import { ApiTags } from "@nestjs/swagger";
+import { AuthService } from "./auth.service";
+import { Credentials } from "./Credentials";
+import { UserInfo } from "./UserInfo";
+
+@ApiTags("auth")
+@Controller()
+export class AuthController {
+ constructor(private readonly authService: AuthService) {}
+ @Post("login")
+ async login(@Body() body: Credentials): Promise {
+ return this.authService.login(body);
+ }
+}
diff --git a/server/src/auth/auth.module.ts b/server/src/auth/auth.module.ts
new file mode 100644
index 0000000..f827706
--- /dev/null
+++ b/server/src/auth/auth.module.ts
@@ -0,0 +1,60 @@
+import { forwardRef, Module } from "@nestjs/common";
+import { ConfigService } from "@nestjs/config";
+import { JwtModule } from "@nestjs/jwt";
+import { PassportModule } from "@nestjs/passport";
+import { JWT_EXPIRATION, JWT_SECRET_KEY } from "../constants";
+import { SecretsManagerModule } from "../providers/secrets/secretsManager.module";
+import { SecretsManagerService } from "../providers/secrets/secretsManager.service";
+// @ts-ignore
+// eslint-disable-next-line
+import { UserModule } from "../user/user.module";
+import { AuthController } from "./auth.controller";
+import { AuthResolver } from "./auth.resolver";
+import { AuthService } from "./auth.service";
+import { BasicStrategy } from "./basic/basic.strategy";
+import { JwtStrategy } from "./jwt/jwt.strategy";
+import { jwtSecretFactory } from "./jwt/jwtSecretFactory";
+import { PasswordService } from "./password.service";
+//@ts-ignore
+import { TokenService } from "./token.service";
+
+@Module({
+ imports: [
+ forwardRef(() => UserModule),
+ PassportModule,
+ SecretsManagerModule,
+ JwtModule.registerAsync({
+ imports: [SecretsManagerModule],
+ inject: [SecretsManagerService, ConfigService],
+ useFactory: async (
+ secretsService: SecretsManagerService,
+ configService: ConfigService
+ ) => {
+ const secret = await secretsService.getSecret(JWT_SECRET_KEY);
+ const expiresIn = configService.get(JWT_EXPIRATION);
+ if (!secret) {
+ throw new Error("Didn't get a valid jwt secret");
+ }
+ if (!expiresIn) {
+ throw new Error("Jwt expire in value is not valid");
+ }
+ return {
+ secret: secret,
+ signOptions: { expiresIn },
+ };
+ },
+ }),
+ ],
+ providers: [
+ AuthService,
+ BasicStrategy,
+ PasswordService,
+ AuthResolver,
+ JwtStrategy,
+ jwtSecretFactory,
+ TokenService,
+ ],
+ controllers: [AuthController],
+ exports: [AuthService, PasswordService],
+})
+export class AuthModule {}
diff --git a/server/src/auth/auth.resolver.ts b/server/src/auth/auth.resolver.ts
new file mode 100644
index 0000000..fb4d6dd
--- /dev/null
+++ b/server/src/auth/auth.resolver.ts
@@ -0,0 +1,23 @@
+import * as common from "@nestjs/common";
+import { Args, Mutation, Query, Resolver } from "@nestjs/graphql";
+import * as gqlACGuard from "../auth/gqlAC.guard";
+import { AuthService } from "./auth.service";
+import { GqlDefaultAuthGuard } from "./gqlDefaultAuth.guard";
+import { UserData } from "./userData.decorator";
+import { LoginArgs } from "./LoginArgs";
+import { UserInfo } from "./UserInfo";
+
+@Resolver(UserInfo)
+export class AuthResolver {
+ constructor(private readonly authService: AuthService) {}
+ @Mutation(() => UserInfo)
+ async login(@Args() args: LoginArgs): Promise {
+ return this.authService.login(args.credentials);
+ }
+
+ @Query(() => UserInfo)
+ @common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+ async userInfo(@UserData() userInfo: UserInfo): Promise {
+ return userInfo;
+ }
+}
diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts
new file mode 100644
index 0000000..1d7d531
--- /dev/null
+++ b/server/src/auth/auth.service.ts
@@ -0,0 +1,52 @@
+import { Injectable, UnauthorizedException } from "@nestjs/common";
+// @ts-ignore
+// eslint-disable-next-line
+import { UserService } from "../user/user.service";
+import { Credentials } from "./Credentials";
+import { PasswordService } from "./password.service";
+import { TokenService } from "./token.service";
+import { UserInfo } from "./UserInfo";
+
+@Injectable()
+export class AuthService {
+ constructor(
+ private readonly userService: UserService,
+ private readonly passwordService: PasswordService,
+ private readonly tokenService: TokenService
+ ) {}
+
+ async validateUser(
+ username: string,
+ password: string
+ ): Promise {
+ const user = await this.userService.findOne({
+ where: { username },
+ });
+ if (user && (await this.passwordService.compare(password, user.password))) {
+ const { id, roles } = user;
+ const roleList = roles as string[];
+ return { id, username, roles: roleList };
+ }
+ return null;
+ }
+ async login(credentials: Credentials): Promise {
+ const { username, password } = credentials;
+ const user = await this.validateUser(
+ credentials.username,
+ credentials.password
+ );
+ if (!user) {
+ throw new UnauthorizedException("The passed credentials are incorrect");
+ }
+ //@ts-ignore
+ const accessToken = await this.tokenService.createToken({
+ id: user.id,
+ username,
+ password,
+ });
+ return {
+ accessToken,
+ ...user,
+ };
+ }
+}
diff --git a/server/src/auth/base/token.service.base.ts b/server/src/auth/base/token.service.base.ts
new file mode 100644
index 0000000..ecddee0
--- /dev/null
+++ b/server/src/auth/base/token.service.base.ts
@@ -0,0 +1,24 @@
+import { Injectable } from "@nestjs/common";
+import { JwtService } from "@nestjs/jwt";
+import { INVALID_PASSWORD_ERROR, INVALID_USERNAME_ERROR } from "../constants";
+import { ITokenService, ITokenPayload } from "../ITokenService";
+/**
+ * TokenServiceBase is a jwt bearer implementation of ITokenService
+ */
+@Injectable()
+export class TokenServiceBase implements ITokenService {
+ constructor(protected readonly jwtService: JwtService) {}
+ /**
+ *
+ * @object { id: String, username: String, password: String}
+ * @returns a jwt token sign with the username and user id
+ */
+ createToken({ id, username, password }: ITokenPayload): Promise {
+ if (!username) return Promise.reject(INVALID_USERNAME_ERROR);
+ if (!password) return Promise.reject(INVALID_PASSWORD_ERROR);
+ return this.jwtService.signAsync({
+ sub: id,
+ username,
+ });
+ }
+}
diff --git a/server/src/auth/basic/base/basic.strategy.base.ts b/server/src/auth/basic/base/basic.strategy.base.ts
new file mode 100644
index 0000000..72537e5
--- /dev/null
+++ b/server/src/auth/basic/base/basic.strategy.base.ts
@@ -0,0 +1,22 @@
+import { UnauthorizedException } from "@nestjs/common";
+import { PassportStrategy } from "@nestjs/passport";
+import { BasicStrategy as Strategy } from "passport-http";
+import { AuthService } from "../../auth.service";
+import { IAuthStrategy } from "../../IAuthStrategy";
+import { UserInfo } from "../../UserInfo";
+
+export class BasicStrategyBase
+ extends PassportStrategy(Strategy)
+ implements IAuthStrategy {
+ constructor(protected readonly authService: AuthService) {
+ super();
+ }
+
+ async validate(username: string, password: string): Promise {
+ const user = await this.authService.validateUser(username, password);
+ if (!user) {
+ throw new UnauthorizedException();
+ }
+ return user;
+ }
+}
diff --git a/server/src/auth/basic/basic.strategy.ts b/server/src/auth/basic/basic.strategy.ts
new file mode 100644
index 0000000..f087795
--- /dev/null
+++ b/server/src/auth/basic/basic.strategy.ts
@@ -0,0 +1,10 @@
+import { Injectable } from "@nestjs/common";
+import { AuthService } from "../auth.service";
+import { BasicStrategyBase } from "./base/basic.strategy.base";
+
+@Injectable()
+export class BasicStrategy extends BasicStrategyBase {
+ constructor(protected readonly authService: AuthService) {
+ super(authService);
+ }
+}
diff --git a/server/src/auth/basic/basicAuth.guard.ts b/server/src/auth/basic/basicAuth.guard.ts
new file mode 100644
index 0000000..9c92fdd
--- /dev/null
+++ b/server/src/auth/basic/basicAuth.guard.ts
@@ -0,0 +1,3 @@
+import { AuthGuard } from "@nestjs/passport";
+
+export class BasicAuthGuard extends AuthGuard("basic") {}
diff --git a/server/src/auth/constants.ts b/server/src/auth/constants.ts
new file mode 100644
index 0000000..59f9f7d
--- /dev/null
+++ b/server/src/auth/constants.ts
@@ -0,0 +1,2 @@
+export const INVALID_USERNAME_ERROR = "Invalid username";
+export const INVALID_PASSWORD_ERROR = "Invalid password";
diff --git a/server/src/auth/defaultAuth.guard.ts b/server/src/auth/defaultAuth.guard.ts
new file mode 100644
index 0000000..33a530c
--- /dev/null
+++ b/server/src/auth/defaultAuth.guard.ts
@@ -0,0 +1,27 @@
+import { Observable } from "rxjs";
+import { ExecutionContext, Injectable } from "@nestjs/common";
+import { Reflector } from "@nestjs/core";
+import { IS_PUBLIC_KEY } from "../decorators/public.decorator";
+import { JwtAuthGuard } from "./jwt/jwtAuth.guard";
+
+@Injectable()
+export class DefaultAuthGuard extends JwtAuthGuard {
+ constructor(private readonly reflector: Reflector) {
+ super();
+ }
+
+ canActivate(
+ context: ExecutionContext
+ ): boolean | Promise | Observable {
+ const isPublic = this.reflector.get(
+ IS_PUBLIC_KEY,
+ context.getHandler()
+ );
+
+ if (isPublic) {
+ return true;
+ }
+
+ return super.canActivate(context);
+ }
+}
diff --git a/server/src/auth/gqlAC.guard.ts b/server/src/auth/gqlAC.guard.ts
new file mode 100644
index 0000000..dacac55
--- /dev/null
+++ b/server/src/auth/gqlAC.guard.ts
@@ -0,0 +1,11 @@
+import { ExecutionContext } from "@nestjs/common";
+import { GqlExecutionContext } from "@nestjs/graphql";
+import { ACGuard } from "nest-access-control";
+
+export class GqlACGuard extends ACGuard {
+ async getUser(context: ExecutionContext): Promise {
+ const ctx = GqlExecutionContext.create(context);
+ const request = ctx.getContext<{ req: { user: User } }>().req;
+ return request.user;
+ }
+}
diff --git a/server/src/auth/gqlDefaultAuth.guard.ts b/server/src/auth/gqlDefaultAuth.guard.ts
new file mode 100644
index 0000000..17143e1
--- /dev/null
+++ b/server/src/auth/gqlDefaultAuth.guard.ts
@@ -0,0 +1,14 @@
+import { ExecutionContext } from "@nestjs/common";
+import { GqlExecutionContext } from "@nestjs/graphql";
+import type { Request } from "express";
+// @ts-ignore
+// eslint-disable-next-line
+import { DefaultAuthGuard } from "./defaultAuth.guard";
+
+export class GqlDefaultAuthGuard extends DefaultAuthGuard {
+ // This method is required for the interface - do not delete it.
+ getRequest(context: ExecutionContext): Request {
+ const ctx = GqlExecutionContext.create(context);
+ return ctx.getContext<{ req: Request }>().req;
+ }
+}
diff --git a/server/src/auth/gqlUserRoles.decorator.ts b/server/src/auth/gqlUserRoles.decorator.ts
new file mode 100644
index 0000000..5ea256b
--- /dev/null
+++ b/server/src/auth/gqlUserRoles.decorator.ts
@@ -0,0 +1,19 @@
+import { createParamDecorator, ExecutionContext } from "@nestjs/common";
+import { GqlExecutionContext } from "@nestjs/graphql";
+
+/**
+ * Access the user roles from the request object i.e `req.user.roles`.
+ *
+ * You can pass an optional property key to the decorator to get it from the user object
+ * e.g `@UserRoles('permissions')` will return the `req.user.permissions` instead.
+ */
+export const UserRoles = createParamDecorator(
+ (data: string, context: ExecutionContext) => {
+ const ctx = GqlExecutionContext.create(context);
+ const request = ctx.getContext<{ req: { user: any } }>().req;
+ if (!request.user) {
+ return null;
+ }
+ return data ? request.user[data] : request.user.roles;
+ }
+);
diff --git a/server/src/auth/jwt/base/jwt.strategy.base.ts b/server/src/auth/jwt/base/jwt.strategy.base.ts
new file mode 100644
index 0000000..7766538
--- /dev/null
+++ b/server/src/auth/jwt/base/jwt.strategy.base.ts
@@ -0,0 +1,40 @@
+import { UnauthorizedException } from "@nestjs/common";
+import { PassportStrategy } from "@nestjs/passport";
+import { ExtractJwt, Strategy } from "passport-jwt";
+import { IAuthStrategy } from "../../IAuthStrategy";
+import { UserService } from "../../../user/user.service";
+import { UserInfo } from "../../UserInfo";
+
+export class JwtStrategyBase
+ extends PassportStrategy(Strategy)
+ implements IAuthStrategy
+{
+ constructor(
+ protected readonly userService: UserService,
+ protected readonly secretOrKey: string
+ ) {
+ super({
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
+ ignoreExpiration: false,
+ secretOrKey,
+ });
+ }
+
+ async validate(payload: UserInfo): Promise {
+ const { username } = payload;
+ const user = await this.userService.findOne({
+ where: { username },
+ });
+ if (!user) {
+ throw new UnauthorizedException();
+ }
+ if (
+ !Array.isArray(user.roles) ||
+ typeof user.roles !== "object" ||
+ user.roles === null
+ ) {
+ throw new Error("User roles is not a valid value");
+ }
+ return { ...user, roles: user.roles as string[] };
+ }
+}
diff --git a/server/src/auth/jwt/jwt.strategy.ts b/server/src/auth/jwt/jwt.strategy.ts
new file mode 100644
index 0000000..0de5def
--- /dev/null
+++ b/server/src/auth/jwt/jwt.strategy.ts
@@ -0,0 +1,13 @@
+import { Inject, Injectable } from "@nestjs/common";
+import { JWT_SECRET_KEY } from "../../constants";
+import { UserService } from "../../user/user.service";
+import { JwtStrategyBase } from "./base/jwt.strategy.base";
+@Injectable()
+export class JwtStrategy extends JwtStrategyBase {
+ constructor(
+ protected readonly userService: UserService,
+ @Inject(JWT_SECRET_KEY) secretOrKey: string
+ ) {
+ super(userService, secretOrKey);
+ }
+}
diff --git a/server/src/auth/jwt/jwtAuth.guard.ts b/server/src/auth/jwt/jwtAuth.guard.ts
new file mode 100644
index 0000000..f0c5570
--- /dev/null
+++ b/server/src/auth/jwt/jwtAuth.guard.ts
@@ -0,0 +1,3 @@
+import { AuthGuard } from "@nestjs/passport";
+
+export class JwtAuthGuard extends AuthGuard("jwt") {}
diff --git a/server/src/auth/jwt/jwtSecretFactory.ts b/server/src/auth/jwt/jwtSecretFactory.ts
new file mode 100644
index 0000000..151e793
--- /dev/null
+++ b/server/src/auth/jwt/jwtSecretFactory.ts
@@ -0,0 +1,16 @@
+import { JWT_SECRET_KEY } from "../../constants";
+import { SecretsManagerService } from "../../providers/secrets/secretsManager.service";
+
+export const jwtSecretFactory = {
+ provide: JWT_SECRET_KEY,
+ useFactory: async (
+ secretsService: SecretsManagerService
+ ): Promise => {
+ const secret = await secretsService.getSecret(JWT_SECRET_KEY);
+ if (secret) {
+ return secret;
+ }
+ throw new Error("jwtSecretFactory missing secret");
+ },
+ inject: [SecretsManagerService],
+};
diff --git a/server/src/auth/password.service.ts b/server/src/auth/password.service.ts
new file mode 100644
index 0000000..377b64b
--- /dev/null
+++ b/server/src/auth/password.service.ts
@@ -0,0 +1,64 @@
+import { Injectable } from "@nestjs/common";
+import { hash, compare } from "bcrypt";
+import { ConfigService } from "@nestjs/config";
+
+/** Salt or number of rounds to generate a salt */
+export type Salt = string | number;
+
+const BCRYPT_SALT_VAR = "BCRYPT_SALT";
+const UNDEFINED_SALT_OR_ROUNDS_ERROR = `${BCRYPT_SALT_VAR} is not defined`;
+const SALT_OR_ROUNDS_TYPE_ERROR = `${BCRYPT_SALT_VAR} must be a positive integer or text`;
+
+@Injectable()
+export class PasswordService {
+ /**
+ * the salt to be used to hash the password. if specified as a number then a
+ * salt will be generated with the specified number of rounds and used
+ */
+ salt: Salt;
+
+ constructor(private configService: ConfigService) {
+ const saltOrRounds = this.configService.get(BCRYPT_SALT_VAR);
+ this.salt = parseSalt(saltOrRounds);
+ }
+
+ /**
+ *
+ * @param password the password to be encrypted.
+ * @param encrypted the encrypted password to be compared against.
+ * @returns whether the password match the encrypted password
+ */
+ compare(password: string, encrypted: string): Promise {
+ return compare(password, encrypted);
+ }
+
+ /**
+ * @param password the password to be encrypted
+ * @return encrypted password
+ */
+ hash(password: string): Promise {
+ return hash(password, this.salt);
+ }
+}
+
+/**
+ * Parses a salt environment variable value.
+ * If a number string value is given tries to parse it as a number of rounds to generate a salt
+ * @param value salt environment variable value
+ * @returns salt or number of rounds to generate a salt
+ */
+export function parseSalt(value: string | undefined): Salt {
+ if (value === undefined) {
+ throw new Error(UNDEFINED_SALT_OR_ROUNDS_ERROR);
+ }
+
+ const rounds = Number(value);
+
+ if (Number.isNaN(rounds)) {
+ return value;
+ }
+ if (!Number.isInteger(rounds) || rounds < 0) {
+ throw new Error(SALT_OR_ROUNDS_TYPE_ERROR);
+ }
+ return rounds;
+}
diff --git a/server/src/auth/token.service.ts b/server/src/auth/token.service.ts
new file mode 100644
index 0000000..f789d61
--- /dev/null
+++ b/server/src/auth/token.service.ts
@@ -0,0 +1,6 @@
+import { ITokenService } from "./ITokenService";
+//@ts-ignore
+// eslint-disable-next-line import/no-unresolved
+import { TokenServiceBase } from "./base/token.service.base";
+//@ts-ignore
+export class TokenService extends TokenServiceBase implements ITokenService {}
diff --git a/server/src/auth/userData.decorator.ts b/server/src/auth/userData.decorator.ts
new file mode 100644
index 0000000..240c7d9
--- /dev/null
+++ b/server/src/auth/userData.decorator.ts
@@ -0,0 +1,31 @@
+import { createParamDecorator, ExecutionContext } from "@nestjs/common";
+import { GqlContextType, GqlExecutionContext } from "@nestjs/graphql";
+//@ts-ignore
+import { User } from "@prisma/client";
+
+/**
+ * Access the user data from the request object i.e `req.user`.
+ */
+function userFactory(ctx: ExecutionContext): User {
+ const contextType = ctx.getType();
+ if (contextType === "http") {
+ // do something that is only important in the context of regular HTTP requests (REST)
+ const { user } = ctx.switchToHttp().getRequest();
+ return user;
+ } else if (contextType === "rpc") {
+ // do something that is only important in the context of Microservice requests
+ throw new Error("Rpc context is not implemented yet");
+ } else if (contextType === "ws") {
+ // do something that is only important in the context of Websockets requests
+ throw new Error("Websockets context is not implemented yet");
+ } else if (ctx.getType() === "graphql") {
+ // do something that is only important in the context of GraphQL requests
+ const gqlExecutionContext = GqlExecutionContext.create(ctx);
+ return gqlExecutionContext.getContext().req.user;
+ }
+ throw new Error("Invalid context");
+}
+
+export const UserData = createParamDecorator(
+ (data, ctx: ExecutionContext) => userFactory(ctx)
+);
diff --git a/server/src/constants.ts b/server/src/constants.ts
new file mode 100644
index 0000000..08f98bf
--- /dev/null
+++ b/server/src/constants.ts
@@ -0,0 +1,2 @@
+export const JWT_SECRET_KEY = "JWT_SECRET_KEY";
+export const JWT_EXPIRATION = "JWT_EXPIRATION";
diff --git a/server/src/customer/base/CreateCustomerArgs.ts b/server/src/customer/base/CreateCustomerArgs.ts
new file mode 100644
index 0000000..9a72a7f
--- /dev/null
+++ b/server/src/customer/base/CreateCustomerArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { CustomerCreateInput } from "./CustomerCreateInput";
+
+@ArgsType()
+class CreateCustomerArgs {
+ @Field(() => CustomerCreateInput, { nullable: false })
+ data!: CustomerCreateInput;
+}
+
+export { CreateCustomerArgs };
diff --git a/server/src/customer/base/Customer.ts b/server/src/customer/base/Customer.ts
new file mode 100644
index 0000000..9af0875
--- /dev/null
+++ b/server/src/customer/base/Customer.ts
@@ -0,0 +1,106 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ObjectType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { Address } from "../../address/base/Address";
+import { ValidateNested, IsOptional, IsDate, IsString } from "class-validator";
+import { Type } from "class-transformer";
+import { Order } from "../../order/base/Order";
+@ObjectType()
+class Customer {
+ @ApiProperty({
+ required: false,
+ type: () => Address,
+ })
+ @ValidateNested()
+ @Type(() => Address)
+ @IsOptional()
+ address?: Address | null;
+
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ createdAt!: Date;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ email!: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ firstName!: string | null;
+
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ lastName!: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => [Order],
+ })
+ @ValidateNested()
+ @Type(() => Order)
+ @IsOptional()
+ orders?: Array;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ phone!: string | null;
+
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ updatedAt!: Date;
+}
+export { Customer };
diff --git a/server/src/customer/base/CustomerCreateInput.ts b/server/src/customer/base/CustomerCreateInput.ts
new file mode 100644
index 0000000..10bd158
--- /dev/null
+++ b/server/src/customer/base/CustomerCreateInput.ts
@@ -0,0 +1,88 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { AddressWhereUniqueInput } from "../../address/base/AddressWhereUniqueInput";
+import { ValidateNested, IsOptional, IsString } from "class-validator";
+import { Type } from "class-transformer";
+import { OrderCreateNestedManyWithoutCustomersInput } from "./OrderCreateNestedManyWithoutCustomersInput";
+@InputType()
+class CustomerCreateInput {
+ @ApiProperty({
+ required: false,
+ type: () => AddressWhereUniqueInput,
+ })
+ @ValidateNested()
+ @Type(() => AddressWhereUniqueInput)
+ @IsOptional()
+ @Field(() => AddressWhereUniqueInput, {
+ nullable: true,
+ })
+ address?: AddressWhereUniqueInput | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ email?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ firstName?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ lastName?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => OrderCreateNestedManyWithoutCustomersInput,
+ })
+ @ValidateNested()
+ @Type(() => OrderCreateNestedManyWithoutCustomersInput)
+ @IsOptional()
+ @Field(() => OrderCreateNestedManyWithoutCustomersInput, {
+ nullable: true,
+ })
+ orders?: OrderCreateNestedManyWithoutCustomersInput;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ phone?: string | null;
+}
+export { CustomerCreateInput };
diff --git a/server/src/customer/base/CustomerFindManyArgs.ts b/server/src/customer/base/CustomerFindManyArgs.ts
new file mode 100644
index 0000000..85c48e8
--- /dev/null
+++ b/server/src/customer/base/CustomerFindManyArgs.ts
@@ -0,0 +1,53 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { CustomerWhereInput } from "./CustomerWhereInput";
+import { Type } from "class-transformer";
+import { CustomerOrderByInput } from "./CustomerOrderByInput";
+
+@ArgsType()
+class CustomerFindManyArgs {
+ @ApiProperty({
+ required: false,
+ type: () => CustomerWhereInput,
+ })
+ @Field(() => CustomerWhereInput, { nullable: true })
+ @Type(() => CustomerWhereInput)
+ where?: CustomerWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: [CustomerOrderByInput],
+ })
+ @Field(() => [CustomerOrderByInput], { nullable: true })
+ @Type(() => CustomerOrderByInput)
+ orderBy?: Array;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ skip?: number;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ take?: number;
+}
+
+export { CustomerFindManyArgs };
diff --git a/server/src/customer/base/CustomerFindUniqueArgs.ts b/server/src/customer/base/CustomerFindUniqueArgs.ts
new file mode 100644
index 0000000..5514612
--- /dev/null
+++ b/server/src/customer/base/CustomerFindUniqueArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { CustomerWhereUniqueInput } from "./CustomerWhereUniqueInput";
+
+@ArgsType()
+class CustomerFindUniqueArgs {
+ @Field(() => CustomerWhereUniqueInput, { nullable: false })
+ where!: CustomerWhereUniqueInput;
+}
+
+export { CustomerFindUniqueArgs };
diff --git a/server/src/customer/base/CustomerListRelationFilter.ts b/server/src/customer/base/CustomerListRelationFilter.ts
new file mode 100644
index 0000000..5f5be2b
--- /dev/null
+++ b/server/src/customer/base/CustomerListRelationFilter.ts
@@ -0,0 +1,56 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { CustomerWhereInput } from "./CustomerWhereInput";
+import { ValidateNested, IsOptional } from "class-validator";
+import { Type } from "class-transformer";
+
+@InputType()
+class CustomerListRelationFilter {
+ @ApiProperty({
+ required: false,
+ type: () => CustomerWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => CustomerWhereInput)
+ @IsOptional()
+ @Field(() => CustomerWhereInput, {
+ nullable: true,
+ })
+ every?: CustomerWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => CustomerWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => CustomerWhereInput)
+ @IsOptional()
+ @Field(() => CustomerWhereInput, {
+ nullable: true,
+ })
+ some?: CustomerWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => CustomerWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => CustomerWhereInput)
+ @IsOptional()
+ @Field(() => CustomerWhereInput, {
+ nullable: true,
+ })
+ none?: CustomerWhereInput;
+}
+export { CustomerListRelationFilter };
diff --git a/server/src/customer/base/CustomerOrderByInput.ts b/server/src/customer/base/CustomerOrderByInput.ts
new file mode 100644
index 0000000..164e4ff
--- /dev/null
+++ b/server/src/customer/base/CustomerOrderByInput.ts
@@ -0,0 +1,94 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { SortOrder } from "../../util/SortOrder";
+
+@InputType({
+ isAbstract: true,
+ description: undefined,
+})
+class CustomerOrderByInput {
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ addressId?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ createdAt?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ email?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ firstName?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ id?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ lastName?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ phone?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ updatedAt?: SortOrder;
+}
+
+export { CustomerOrderByInput };
diff --git a/server/src/customer/base/CustomerUpdateInput.ts b/server/src/customer/base/CustomerUpdateInput.ts
new file mode 100644
index 0000000..a382668
--- /dev/null
+++ b/server/src/customer/base/CustomerUpdateInput.ts
@@ -0,0 +1,88 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { AddressWhereUniqueInput } from "../../address/base/AddressWhereUniqueInput";
+import { ValidateNested, IsOptional, IsString } from "class-validator";
+import { Type } from "class-transformer";
+import { OrderUpdateManyWithoutCustomersInput } from "./OrderUpdateManyWithoutCustomersInput";
+@InputType()
+class CustomerUpdateInput {
+ @ApiProperty({
+ required: false,
+ type: () => AddressWhereUniqueInput,
+ })
+ @ValidateNested()
+ @Type(() => AddressWhereUniqueInput)
+ @IsOptional()
+ @Field(() => AddressWhereUniqueInput, {
+ nullable: true,
+ })
+ address?: AddressWhereUniqueInput | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ email?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ firstName?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ lastName?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => OrderUpdateManyWithoutCustomersInput,
+ })
+ @ValidateNested()
+ @Type(() => OrderUpdateManyWithoutCustomersInput)
+ @IsOptional()
+ @Field(() => OrderUpdateManyWithoutCustomersInput, {
+ nullable: true,
+ })
+ orders?: OrderUpdateManyWithoutCustomersInput;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ phone?: string | null;
+}
+export { CustomerUpdateInput };
diff --git a/server/src/customer/base/CustomerWhereInput.ts b/server/src/customer/base/CustomerWhereInput.ts
new file mode 100644
index 0000000..1600811
--- /dev/null
+++ b/server/src/customer/base/CustomerWhereInput.ts
@@ -0,0 +1,101 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { AddressWhereUniqueInput } from "../../address/base/AddressWhereUniqueInput";
+import { ValidateNested, IsOptional } from "class-validator";
+import { Type } from "class-transformer";
+import { StringNullableFilter } from "../../util/StringNullableFilter";
+import { StringFilter } from "../../util/StringFilter";
+import { OrderListRelationFilter } from "../../order/base/OrderListRelationFilter";
+@InputType()
+class CustomerWhereInput {
+ @ApiProperty({
+ required: false,
+ type: () => AddressWhereUniqueInput,
+ })
+ @ValidateNested()
+ @Type(() => AddressWhereUniqueInput)
+ @IsOptional()
+ @Field(() => AddressWhereUniqueInput, {
+ nullable: true,
+ })
+ address?: AddressWhereUniqueInput;
+
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ email?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ firstName?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringFilter,
+ })
+ @Type(() => StringFilter)
+ @IsOptional()
+ @Field(() => StringFilter, {
+ nullable: true,
+ })
+ id?: StringFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ lastName?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: () => OrderListRelationFilter,
+ })
+ @ValidateNested()
+ @Type(() => OrderListRelationFilter)
+ @IsOptional()
+ @Field(() => OrderListRelationFilter, {
+ nullable: true,
+ })
+ orders?: OrderListRelationFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ phone?: StringNullableFilter;
+}
+export { CustomerWhereInput };
diff --git a/server/src/customer/base/CustomerWhereUniqueInput.ts b/server/src/customer/base/CustomerWhereUniqueInput.ts
new file mode 100644
index 0000000..044c181
--- /dev/null
+++ b/server/src/customer/base/CustomerWhereUniqueInput.ts
@@ -0,0 +1,25 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsString } from "class-validator";
+@InputType()
+class CustomerWhereUniqueInput {
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+}
+export { CustomerWhereUniqueInput };
diff --git a/server/src/customer/base/DeleteCustomerArgs.ts b/server/src/customer/base/DeleteCustomerArgs.ts
new file mode 100644
index 0000000..a1a23b0
--- /dev/null
+++ b/server/src/customer/base/DeleteCustomerArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { CustomerWhereUniqueInput } from "./CustomerWhereUniqueInput";
+
+@ArgsType()
+class DeleteCustomerArgs {
+ @Field(() => CustomerWhereUniqueInput, { nullable: false })
+ where!: CustomerWhereUniqueInput;
+}
+
+export { DeleteCustomerArgs };
diff --git a/server/src/customer/base/OrderCreateNestedManyWithoutCustomersInput.ts b/server/src/customer/base/OrderCreateNestedManyWithoutCustomersInput.ts
new file mode 100644
index 0000000..8e0350a
--- /dev/null
+++ b/server/src/customer/base/OrderCreateNestedManyWithoutCustomersInput.ts
@@ -0,0 +1,26 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { OrderWhereUniqueInput } from "../../order/base/OrderWhereUniqueInput";
+import { ApiProperty } from "@nestjs/swagger";
+@InputType()
+class OrderCreateNestedManyWithoutCustomersInput {
+ @Field(() => [OrderWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [OrderWhereUniqueInput],
+ })
+ connect?: Array;
+}
+export { OrderCreateNestedManyWithoutCustomersInput };
diff --git a/server/src/customer/base/OrderUpdateManyWithoutCustomersInput.ts b/server/src/customer/base/OrderUpdateManyWithoutCustomersInput.ts
new file mode 100644
index 0000000..e27d70c
--- /dev/null
+++ b/server/src/customer/base/OrderUpdateManyWithoutCustomersInput.ts
@@ -0,0 +1,44 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { OrderWhereUniqueInput } from "../../order/base/OrderWhereUniqueInput";
+import { ApiProperty } from "@nestjs/swagger";
+@InputType()
+class OrderUpdateManyWithoutCustomersInput {
+ @Field(() => [OrderWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [OrderWhereUniqueInput],
+ })
+ connect?: Array;
+
+ @Field(() => [OrderWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [OrderWhereUniqueInput],
+ })
+ disconnect?: Array;
+
+ @Field(() => [OrderWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [OrderWhereUniqueInput],
+ })
+ set?: Array;
+}
+export { OrderUpdateManyWithoutCustomersInput };
diff --git a/server/src/customer/base/UpdateCustomerArgs.ts b/server/src/customer/base/UpdateCustomerArgs.ts
new file mode 100644
index 0000000..a89b20b
--- /dev/null
+++ b/server/src/customer/base/UpdateCustomerArgs.ts
@@ -0,0 +1,24 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { CustomerWhereUniqueInput } from "./CustomerWhereUniqueInput";
+import { CustomerUpdateInput } from "./CustomerUpdateInput";
+
+@ArgsType()
+class UpdateCustomerArgs {
+ @Field(() => CustomerWhereUniqueInput, { nullable: false })
+ where!: CustomerWhereUniqueInput;
+ @Field(() => CustomerUpdateInput, { nullable: false })
+ data!: CustomerUpdateInput;
+}
+
+export { UpdateCustomerArgs };
diff --git a/server/src/customer/base/customer.controller.base.spec.ts b/server/src/customer/base/customer.controller.base.spec.ts
new file mode 100644
index 0000000..b53a239
--- /dev/null
+++ b/server/src/customer/base/customer.controller.base.spec.ts
@@ -0,0 +1,185 @@
+import { Test } from "@nestjs/testing";
+import {
+ INestApplication,
+ HttpStatus,
+ ExecutionContext,
+ CallHandler,
+} from "@nestjs/common";
+import request from "supertest";
+import { MorganModule } from "nest-morgan";
+import { ACGuard } from "nest-access-control";
+import { DefaultAuthGuard } from "../../auth/defaultAuth.guard";
+import { ACLModule } from "../../auth/acl.module";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { map } from "rxjs";
+import { CustomerController } from "../customer.controller";
+import { CustomerService } from "../customer.service";
+
+const nonExistingId = "nonExistingId";
+const existingId = "existingId";
+const CREATE_INPUT = {
+ createdAt: new Date(),
+ email: "exampleEmail",
+ firstName: "exampleFirstName",
+ id: "exampleId",
+ lastName: "exampleLastName",
+ phone: "examplePhone",
+ updatedAt: new Date(),
+};
+const CREATE_RESULT = {
+ createdAt: new Date(),
+ email: "exampleEmail",
+ firstName: "exampleFirstName",
+ id: "exampleId",
+ lastName: "exampleLastName",
+ phone: "examplePhone",
+ updatedAt: new Date(),
+};
+const FIND_MANY_RESULT = [
+ {
+ createdAt: new Date(),
+ email: "exampleEmail",
+ firstName: "exampleFirstName",
+ id: "exampleId",
+ lastName: "exampleLastName",
+ phone: "examplePhone",
+ updatedAt: new Date(),
+ },
+];
+const FIND_ONE_RESULT = {
+ createdAt: new Date(),
+ email: "exampleEmail",
+ firstName: "exampleFirstName",
+ id: "exampleId",
+ lastName: "exampleLastName",
+ phone: "examplePhone",
+ updatedAt: new Date(),
+};
+
+const service = {
+ create() {
+ return CREATE_RESULT;
+ },
+ findMany: () => FIND_MANY_RESULT,
+ findOne: ({ where }: { where: { id: string } }) => {
+ switch (where.id) {
+ case existingId:
+ return FIND_ONE_RESULT;
+ case nonExistingId:
+ return null;
+ }
+ },
+};
+
+const basicAuthGuard = {
+ canActivate: (context: ExecutionContext) => {
+ const argumentHost = context.switchToHttp();
+ const request = argumentHost.getRequest();
+ request.user = {
+ roles: ["user"],
+ };
+ return true;
+ },
+};
+
+const acGuard = {
+ canActivate: () => {
+ return true;
+ },
+};
+
+const aclFilterResponseInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle().pipe(
+ map((data) => {
+ return data;
+ })
+ );
+ },
+};
+const aclValidateRequestInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle();
+ },
+};
+
+describe("Customer", () => {
+ let app: INestApplication;
+
+ beforeAll(async () => {
+ const moduleRef = await Test.createTestingModule({
+ providers: [
+ {
+ provide: CustomerService,
+ useValue: service,
+ },
+ ],
+ controllers: [CustomerController],
+ imports: [MorganModule.forRoot(), ACLModule],
+ })
+ .overrideGuard(DefaultAuthGuard)
+ .useValue(basicAuthGuard)
+ .overrideGuard(ACGuard)
+ .useValue(acGuard)
+ .overrideInterceptor(AclFilterResponseInterceptor)
+ .useValue(aclFilterResponseInterceptor)
+ .overrideInterceptor(AclValidateRequestInterceptor)
+ .useValue(aclValidateRequestInterceptor)
+ .compile();
+
+ app = moduleRef.createNestApplication();
+ await app.init();
+ });
+
+ test("POST /customers", async () => {
+ await request(app.getHttpServer())
+ .post("/customers")
+ .send(CREATE_INPUT)
+ .expect(HttpStatus.CREATED)
+ .expect({
+ ...CREATE_RESULT,
+ createdAt: CREATE_RESULT.createdAt.toISOString(),
+ updatedAt: CREATE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ test("GET /customers", async () => {
+ await request(app.getHttpServer())
+ .get("/customers")
+ .expect(HttpStatus.OK)
+ .expect([
+ {
+ ...FIND_MANY_RESULT[0],
+ createdAt: FIND_MANY_RESULT[0].createdAt.toISOString(),
+ updatedAt: FIND_MANY_RESULT[0].updatedAt.toISOString(),
+ },
+ ]);
+ });
+
+ test("GET /customers/:id non existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/customers"}/${nonExistingId}`)
+ .expect(HttpStatus.NOT_FOUND)
+ .expect({
+ statusCode: HttpStatus.NOT_FOUND,
+ message: `No resource was found for {"${"id"}":"${nonExistingId}"}`,
+ error: "Not Found",
+ });
+ });
+
+ test("GET /customers/:id existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/customers"}/${existingId}`)
+ .expect(HttpStatus.OK)
+ .expect({
+ ...FIND_ONE_RESULT,
+ createdAt: FIND_ONE_RESULT.createdAt.toISOString(),
+ updatedAt: FIND_ONE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ afterAll(async () => {
+ await app.close();
+ });
+});
diff --git a/server/src/customer/base/customer.controller.base.ts b/server/src/customer/base/customer.controller.base.ts
new file mode 100644
index 0000000..847a6d1
--- /dev/null
+++ b/server/src/customer/base/customer.controller.base.ts
@@ -0,0 +1,354 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as swagger from "@nestjs/swagger";
+import * as nestAccessControl from "nest-access-control";
+import * as defaultAuthGuard from "../../auth/defaultAuth.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import * as errors from "../../errors";
+import { Request } from "express";
+import { plainToClass } from "class-transformer";
+import { ApiNestedQuery } from "../../decorators/api-nested-query.decorator";
+import { CustomerService } from "../customer.service";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { CustomerCreateInput } from "./CustomerCreateInput";
+import { CustomerWhereInput } from "./CustomerWhereInput";
+import { CustomerWhereUniqueInput } from "./CustomerWhereUniqueInput";
+import { CustomerFindManyArgs } from "./CustomerFindManyArgs";
+import { CustomerUpdateInput } from "./CustomerUpdateInput";
+import { Customer } from "./Customer";
+import { OrderFindManyArgs } from "../../order/base/OrderFindManyArgs";
+import { Order } from "../../order/base/Order";
+import { OrderWhereUniqueInput } from "../../order/base/OrderWhereUniqueInput";
+@swagger.ApiBearerAuth()
+@common.UseGuards(defaultAuthGuard.DefaultAuthGuard, nestAccessControl.ACGuard)
+export class CustomerControllerBase {
+ constructor(
+ protected readonly service: CustomerService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "create",
+ possession: "any",
+ })
+ @common.Post()
+ @swagger.ApiCreatedResponse({ type: Customer })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async create(@common.Body() data: CustomerCreateInput): Promise {
+ return await this.service.create({
+ data: {
+ ...data,
+
+ address: data.address
+ ? {
+ connect: data.address,
+ }
+ : undefined,
+ },
+ select: {
+ address: {
+ select: {
+ id: true,
+ },
+ },
+
+ createdAt: true,
+ email: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ phone: true,
+ updatedAt: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "read",
+ possession: "any",
+ })
+ @common.Get()
+ @swagger.ApiOkResponse({ type: [Customer] })
+ @swagger.ApiForbiddenResponse()
+ @ApiNestedQuery(CustomerFindManyArgs)
+ async findMany(@common.Req() request: Request): Promise {
+ const args = plainToClass(CustomerFindManyArgs, request.query);
+ return this.service.findMany({
+ ...args,
+ select: {
+ address: {
+ select: {
+ id: true,
+ },
+ },
+
+ createdAt: true,
+ email: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ phone: true,
+ updatedAt: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "read",
+ possession: "own",
+ })
+ @common.Get("/:id")
+ @swagger.ApiOkResponse({ type: Customer })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async findOne(
+ @common.Param() params: CustomerWhereUniqueInput
+ ): Promise {
+ const result = await this.service.findOne({
+ where: params,
+ select: {
+ address: {
+ select: {
+ id: true,
+ },
+ },
+
+ createdAt: true,
+ email: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ phone: true,
+ updatedAt: true,
+ },
+ });
+ if (result === null) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "update",
+ possession: "any",
+ })
+ @common.Patch("/:id")
+ @swagger.ApiOkResponse({ type: Customer })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async update(
+ @common.Param() params: CustomerWhereUniqueInput,
+ @common.Body() data: CustomerUpdateInput
+ ): Promise {
+ try {
+ return await this.service.update({
+ where: params,
+ data: {
+ ...data,
+
+ address: data.address
+ ? {
+ connect: data.address,
+ }
+ : undefined,
+ },
+ select: {
+ address: {
+ select: {
+ id: true,
+ },
+ },
+
+ createdAt: true,
+ email: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ phone: true,
+ updatedAt: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "delete",
+ possession: "any",
+ })
+ @common.Delete("/:id")
+ @swagger.ApiOkResponse({ type: Customer })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async delete(
+ @common.Param() params: CustomerWhereUniqueInput
+ ): Promise {
+ try {
+ return await this.service.delete({
+ where: params,
+ select: {
+ address: {
+ select: {
+ id: true,
+ },
+ },
+
+ createdAt: true,
+ email: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ phone: true,
+ updatedAt: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "read",
+ possession: "any",
+ })
+ @common.Get("/:id/orders")
+ @ApiNestedQuery(OrderFindManyArgs)
+ async findManyOrders(
+ @common.Req() request: Request,
+ @common.Param() params: CustomerWhereUniqueInput
+ ): Promise {
+ const query = plainToClass(OrderFindManyArgs, request.query);
+ const results = await this.service.findOrders(params.id, {
+ ...query,
+ select: {
+ createdAt: true,
+
+ customer: {
+ select: {
+ id: true,
+ },
+ },
+
+ discount: true,
+ id: true,
+
+ product: {
+ select: {
+ id: true,
+ },
+ },
+
+ quantity: true,
+ totalPrice: true,
+ updatedAt: true,
+ },
+ });
+ if (results === null) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ return results;
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "update",
+ possession: "any",
+ })
+ @common.Post("/:id/orders")
+ async connectOrders(
+ @common.Param() params: CustomerWhereUniqueInput,
+ @common.Body() body: OrderWhereUniqueInput[]
+ ): Promise {
+ const data = {
+ orders: {
+ connect: body,
+ },
+ };
+ await this.service.update({
+ where: params,
+ data,
+ select: { id: true },
+ });
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "update",
+ possession: "any",
+ })
+ @common.Patch("/:id/orders")
+ async updateOrders(
+ @common.Param() params: CustomerWhereUniqueInput,
+ @common.Body() body: OrderWhereUniqueInput[]
+ ): Promise {
+ const data = {
+ orders: {
+ set: body,
+ },
+ };
+ await this.service.update({
+ where: params,
+ data,
+ select: { id: true },
+ });
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "update",
+ possession: "any",
+ })
+ @common.Delete("/:id/orders")
+ async disconnectOrders(
+ @common.Param() params: CustomerWhereUniqueInput,
+ @common.Body() body: OrderWhereUniqueInput[]
+ ): Promise {
+ const data = {
+ orders: {
+ disconnect: body,
+ },
+ };
+ await this.service.update({
+ where: params,
+ data,
+ select: { id: true },
+ });
+ }
+}
diff --git a/server/src/customer/base/customer.module.base.ts b/server/src/customer/base/customer.module.base.ts
new file mode 100644
index 0000000..c7e7183
--- /dev/null
+++ b/server/src/customer/base/customer.module.base.ts
@@ -0,0 +1,28 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { Module, forwardRef } from "@nestjs/common";
+import { MorganModule } from "nest-morgan";
+import { PrismaModule } from "nestjs-prisma";
+import { ACLModule } from "../../auth/acl.module";
+import { AuthModule } from "../../auth/auth.module";
+
+@Module({
+ imports: [
+ ACLModule,
+ forwardRef(() => AuthModule),
+ MorganModule,
+ PrismaModule,
+ ],
+
+ exports: [ACLModule, AuthModule, MorganModule, PrismaModule],
+})
+export class CustomerModuleBase {}
diff --git a/server/src/customer/base/customer.resolver.base.ts b/server/src/customer/base/customer.resolver.base.ts
new file mode 100644
index 0000000..ad3193b
--- /dev/null
+++ b/server/src/customer/base/customer.resolver.base.ts
@@ -0,0 +1,203 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as graphql from "@nestjs/graphql";
+import * as apollo from "apollo-server-express";
+import * as nestAccessControl from "nest-access-control";
+import { GqlDefaultAuthGuard } from "../../auth/gqlDefaultAuth.guard";
+import * as gqlACGuard from "../../auth/gqlAC.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import { MetaQueryPayload } from "../../util/MetaQueryPayload";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { CreateCustomerArgs } from "./CreateCustomerArgs";
+import { UpdateCustomerArgs } from "./UpdateCustomerArgs";
+import { DeleteCustomerArgs } from "./DeleteCustomerArgs";
+import { CustomerFindManyArgs } from "./CustomerFindManyArgs";
+import { CustomerFindUniqueArgs } from "./CustomerFindUniqueArgs";
+import { Customer } from "./Customer";
+import { OrderFindManyArgs } from "../../order/base/OrderFindManyArgs";
+import { Order } from "../../order/base/Order";
+import { Address } from "../../address/base/Address";
+import { CustomerService } from "../customer.service";
+
+@graphql.Resolver(() => Customer)
+@common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+export class CustomerResolverBase {
+ constructor(
+ protected readonly service: CustomerService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @graphql.Query(() => MetaQueryPayload)
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "read",
+ possession: "any",
+ })
+ async _customersMeta(
+ @graphql.Args() args: CustomerFindManyArgs
+ ): Promise {
+ const results = await this.service.count({
+ ...args,
+ skip: undefined,
+ take: undefined,
+ });
+ return {
+ count: results,
+ };
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => [Customer])
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "read",
+ possession: "any",
+ })
+ async customers(
+ @graphql.Args() args: CustomerFindManyArgs
+ ): Promise {
+ return this.service.findMany(args);
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => Customer, { nullable: true })
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "read",
+ possession: "own",
+ })
+ async customer(
+ @graphql.Args() args: CustomerFindUniqueArgs
+ ): Promise {
+ const result = await this.service.findOne(args);
+ if (result === null) {
+ return null;
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => Customer)
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "create",
+ possession: "any",
+ })
+ async createCustomer(
+ @graphql.Args() args: CreateCustomerArgs
+ ): Promise {
+ return await this.service.create({
+ ...args,
+ data: {
+ ...args.data,
+
+ address: args.data.address
+ ? {
+ connect: args.data.address,
+ }
+ : undefined,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => Customer)
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "update",
+ possession: "any",
+ })
+ async updateCustomer(
+ @graphql.Args() args: UpdateCustomerArgs
+ ): Promise {
+ try {
+ return await this.service.update({
+ ...args,
+ data: {
+ ...args.data,
+
+ address: args.data.address
+ ? {
+ connect: args.data.address,
+ }
+ : undefined,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @graphql.Mutation(() => Customer)
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "delete",
+ possession: "any",
+ })
+ async deleteCustomer(
+ @graphql.Args() args: DeleteCustomerArgs
+ ): Promise {
+ try {
+ return await this.service.delete(args);
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.ResolveField(() => [Order])
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "read",
+ possession: "any",
+ })
+ async orders(
+ @graphql.Parent() parent: Customer,
+ @graphql.Args() args: OrderFindManyArgs
+ ): Promise {
+ const results = await this.service.findOrders(parent.id, args);
+
+ if (!results) {
+ return [];
+ }
+
+ return results;
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.ResolveField(() => Address, { nullable: true })
+ @nestAccessControl.UseRoles({
+ resource: "Address",
+ action: "read",
+ possession: "any",
+ })
+ async address(@graphql.Parent() parent: Customer): Promise {
+ const result = await this.service.getAddress(parent.id);
+
+ if (!result) {
+ return null;
+ }
+ return result;
+ }
+}
diff --git a/server/src/customer/base/customer.service.base.ts b/server/src/customer/base/customer.service.base.ts
new file mode 100644
index 0000000..5a27e21
--- /dev/null
+++ b/server/src/customer/base/customer.service.base.ts
@@ -0,0 +1,68 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { PrismaService } from "nestjs-prisma";
+import { Prisma, Customer, Order, Address } from "@prisma/client";
+
+export class CustomerServiceBase {
+ constructor(protected readonly prisma: PrismaService) {}
+
+ async count(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.customer.count(args);
+ }
+
+ async findMany(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.customer.findMany(args);
+ }
+ async findOne(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.customer.findUnique(args);
+ }
+ async create(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.customer.create(args);
+ }
+ async update(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.customer.update(args);
+ }
+ async delete(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.customer.delete(args);
+ }
+
+ async findOrders(
+ parentId: string,
+ args: Prisma.OrderFindManyArgs
+ ): Promise {
+ return this.prisma.customer
+ .findUnique({
+ where: { id: parentId },
+ })
+ .orders(args);
+ }
+
+ async getAddress(parentId: string): Promise {
+ return this.prisma.customer
+ .findUnique({
+ where: { id: parentId },
+ })
+ .address();
+ }
+}
diff --git a/server/src/customer/customer.controller.ts b/server/src/customer/customer.controller.ts
new file mode 100644
index 0000000..1da4964
--- /dev/null
+++ b/server/src/customer/customer.controller.ts
@@ -0,0 +1,17 @@
+import * as common from "@nestjs/common";
+import * as swagger from "@nestjs/swagger";
+import * as nestAccessControl from "nest-access-control";
+import { CustomerService } from "./customer.service";
+import { CustomerControllerBase } from "./base/customer.controller.base";
+
+@swagger.ApiTags("customers")
+@common.Controller("customers")
+export class CustomerController extends CustomerControllerBase {
+ constructor(
+ protected readonly service: CustomerService,
+ @nestAccessControl.InjectRolesBuilder()
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {
+ super(service, rolesBuilder);
+ }
+}
diff --git a/server/src/customer/customer.module.ts b/server/src/customer/customer.module.ts
new file mode 100644
index 0000000..2115fb8
--- /dev/null
+++ b/server/src/customer/customer.module.ts
@@ -0,0 +1,13 @@
+import { Module } from "@nestjs/common";
+import { CustomerModuleBase } from "./base/customer.module.base";
+import { CustomerService } from "./customer.service";
+import { CustomerController } from "./customer.controller";
+import { CustomerResolver } from "./customer.resolver";
+
+@Module({
+ imports: [CustomerModuleBase],
+ controllers: [CustomerController],
+ providers: [CustomerService, CustomerResolver],
+ exports: [CustomerService],
+})
+export class CustomerModule {}
diff --git a/server/src/customer/customer.resolver.ts b/server/src/customer/customer.resolver.ts
new file mode 100644
index 0000000..83897f9
--- /dev/null
+++ b/server/src/customer/customer.resolver.ts
@@ -0,0 +1,20 @@
+import * as common from "@nestjs/common";
+import * as graphql from "@nestjs/graphql";
+import * as nestAccessControl from "nest-access-control";
+import { GqlDefaultAuthGuard } from "../auth/gqlDefaultAuth.guard";
+import * as gqlACGuard from "../auth/gqlAC.guard";
+import { CustomerResolverBase } from "./base/customer.resolver.base";
+import { Customer } from "./base/Customer";
+import { CustomerService } from "./customer.service";
+
+@graphql.Resolver(() => Customer)
+@common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+export class CustomerResolver extends CustomerResolverBase {
+ constructor(
+ protected readonly service: CustomerService,
+ @nestAccessControl.InjectRolesBuilder()
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {
+ super(service, rolesBuilder);
+ }
+}
diff --git a/server/src/customer/customer.service.ts b/server/src/customer/customer.service.ts
new file mode 100644
index 0000000..9144aeb
--- /dev/null
+++ b/server/src/customer/customer.service.ts
@@ -0,0 +1,10 @@
+import { Injectable } from "@nestjs/common";
+import { PrismaService } from "nestjs-prisma";
+import { CustomerServiceBase } from "./base/customer.service.base";
+
+@Injectable()
+export class CustomerService extends CustomerServiceBase {
+ constructor(protected readonly prisma: PrismaService) {
+ super(prisma);
+ }
+}
diff --git a/server/src/decorators/api-nested-query.decorator.ts b/server/src/decorators/api-nested-query.decorator.ts
new file mode 100644
index 0000000..9fd5ba3
--- /dev/null
+++ b/server/src/decorators/api-nested-query.decorator.ts
@@ -0,0 +1,80 @@
+import { applyDecorators } from "@nestjs/common";
+import {
+ ApiExtraModels,
+ ApiQuery,
+ ApiQueryOptions,
+ getSchemaPath,
+} from "@nestjs/swagger";
+import "reflect-metadata";
+
+const generateApiQueryObject = (
+ prop: any,
+ propType: any,
+ required: boolean,
+ isArray: boolean
+): ApiQueryOptions => {
+ if (propType === Number) {
+ return {
+ required,
+ name: prop,
+ style: "deepObject",
+ explode: true,
+ type: "number",
+ isArray,
+ };
+ } else if (propType === String) {
+ return {
+ required,
+ name: prop,
+ style: "deepObject",
+ explode: true,
+ type: "string",
+ isArray,
+ };
+ } else {
+ return {
+ required,
+ name: prop,
+ style: "deepObject",
+ explode: true,
+ type: "object",
+ isArray,
+ schema: {
+ $ref: getSchemaPath(propType),
+ },
+ };
+ }
+};
+
+// eslint-disable-next-line @typescript-eslint/ban-types,@typescript-eslint/explicit-module-boundary-types,@typescript-eslint/naming-convention
+export function ApiNestedQuery(query: Function) {
+ const constructor = query.prototype;
+ const properties = Reflect.getMetadata(
+ "swagger/apiModelPropertiesArray",
+ constructor
+ ).map((prop: any) => prop.slice(1));
+
+ const decorators = properties
+ .map((property: any) => {
+ const { required, isArray } = Reflect.getMetadata(
+ "swagger/apiModelProperties",
+ constructor,
+ property
+ );
+ const propertyType = Reflect.getMetadata(
+ "design:type",
+ constructor,
+ property
+ );
+ const typedQuery = generateApiQueryObject(
+ property,
+ propertyType,
+ required,
+ isArray
+ );
+ return [ApiExtraModels(propertyType), ApiQuery(typedQuery)];
+ })
+ .flat();
+
+ return applyDecorators(...decorators);
+}
diff --git a/server/src/decorators/public.decorator.ts b/server/src/decorators/public.decorator.ts
new file mode 100644
index 0000000..9eab4e0
--- /dev/null
+++ b/server/src/decorators/public.decorator.ts
@@ -0,0 +1,10 @@
+import { applyDecorators, SetMetadata } from "@nestjs/common";
+
+export const IS_PUBLIC_KEY = "isPublic";
+
+const PublicAuthMiddleware = SetMetadata(IS_PUBLIC_KEY, true);
+const PublicAuthSwagger = SetMetadata("swagger/apiSecurity", ["isPublic"]);
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+export const Public = () =>
+ applyDecorators(PublicAuthMiddleware, PublicAuthSwagger);
diff --git a/server/src/errors.ts b/server/src/errors.ts
new file mode 100644
index 0000000..bd1aa6d
--- /dev/null
+++ b/server/src/errors.ts
@@ -0,0 +1,16 @@
+import * as common from "@nestjs/common";
+import { ApiProperty } from "@nestjs/swagger";
+
+export class ForbiddenException extends common.ForbiddenException {
+ @ApiProperty()
+ statusCode!: number;
+ @ApiProperty()
+ message!: string;
+}
+
+export class NotFoundException extends common.NotFoundException {
+ @ApiProperty()
+ statusCode!: number;
+ @ApiProperty()
+ message!: string;
+}
diff --git a/server/src/grants.json b/server/src/grants.json
new file mode 100644
index 0000000..a9e8ea6
--- /dev/null
+++ b/server/src/grants.json
@@ -0,0 +1,152 @@
+[
+ {
+ "role": "user",
+ "resource": "User",
+ "action": "read:own",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "User",
+ "action": "create:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "User",
+ "action": "update:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "User",
+ "action": "delete:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "User",
+ "action": "read:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Order",
+ "action": "read:own",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Order",
+ "action": "create:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Order",
+ "action": "update:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Order",
+ "action": "delete:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Order",
+ "action": "read:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Customer",
+ "action": "read:own",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Customer",
+ "action": "create:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Customer",
+ "action": "update:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Customer",
+ "action": "delete:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Customer",
+ "action": "read:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Address",
+ "action": "read:own",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Address",
+ "action": "create:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Address",
+ "action": "update:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Address",
+ "action": "delete:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Address",
+ "action": "read:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Product",
+ "action": "read:own",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Product",
+ "action": "create:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Product",
+ "action": "update:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Product",
+ "action": "delete:any",
+ "attributes": "*"
+ },
+ {
+ "role": "user",
+ "resource": "Product",
+ "action": "read:any",
+ "attributes": "*"
+ }
+]
\ No newline at end of file
diff --git a/server/src/health/base/health.controller.base.ts b/server/src/health/base/health.controller.base.ts
new file mode 100644
index 0000000..afd9e0d
--- /dev/null
+++ b/server/src/health/base/health.controller.base.ts
@@ -0,0 +1,19 @@
+import { Get, HttpStatus, Res } from "@nestjs/common";
+import { Response } from "express";
+import { HealthService } from "../health.service";
+
+export class HealthControllerBase {
+ constructor(protected readonly healthService: HealthService) {}
+ @Get("live")
+ healthLive(@Res() response: Response): Response {
+ return response.status(HttpStatus.NO_CONTENT).send();
+ }
+ @Get("ready")
+ async healthReady(@Res() response: Response): Promise> {
+ const dbConnection = await this.healthService.isDbReady();
+ if (!dbConnection) {
+ return response.status(HttpStatus.NOT_FOUND).send();
+ }
+ return response.status(HttpStatus.NO_CONTENT).send();
+ }
+}
diff --git a/server/src/health/base/health.service.base.ts b/server/src/health/base/health.service.base.ts
new file mode 100644
index 0000000..0db85da
--- /dev/null
+++ b/server/src/health/base/health.service.base.ts
@@ -0,0 +1,15 @@
+import { Injectable } from "@nestjs/common";
+import { PrismaService } from "nestjs-prisma";
+
+@Injectable()
+export class HealthServiceBase {
+ constructor(protected readonly prisma: PrismaService) {}
+ async isDbReady(): Promise {
+ try {
+ await this.prisma.$queryRaw`SELECT 1`;
+ return true;
+ } catch (error) {
+ return false;
+ }
+ }
+}
diff --git a/server/src/health/health.controller.ts b/server/src/health/health.controller.ts
new file mode 100644
index 0000000..ff484e7
--- /dev/null
+++ b/server/src/health/health.controller.ts
@@ -0,0 +1,10 @@
+import { Controller } from "@nestjs/common";
+import { HealthControllerBase } from "./base/health.controller.base";
+import { HealthService } from "./health.service";
+
+@Controller("_health")
+export class HealthController extends HealthControllerBase {
+ constructor(protected readonly healthService: HealthService) {
+ super(healthService);
+ }
+}
diff --git a/server/src/health/health.module.ts b/server/src/health/health.module.ts
new file mode 100644
index 0000000..61044a5
--- /dev/null
+++ b/server/src/health/health.module.ts
@@ -0,0 +1,12 @@
+import { Module } from "@nestjs/common";
+import { PrismaModule } from "nestjs-prisma";
+import { HealthController } from "./health.controller";
+import { HealthService } from "./health.service";
+
+@Module({
+ imports: [PrismaModule],
+ controllers: [HealthController],
+ providers: [HealthService],
+ exports: [HealthService],
+})
+export class HealthModule {}
diff --git a/server/src/health/health.service.ts b/server/src/health/health.service.ts
new file mode 100644
index 0000000..257c180
--- /dev/null
+++ b/server/src/health/health.service.ts
@@ -0,0 +1,10 @@
+import { Injectable } from "@nestjs/common";
+import { PrismaService } from "nestjs-prisma";
+import { HealthServiceBase } from "./base/health.service.base";
+
+@Injectable()
+export class HealthService extends HealthServiceBase {
+ constructor(protected readonly prisma: PrismaService) {
+ super(prisma);
+ }
+}
diff --git a/server/src/interceptors/aclFilterResponse.interceptor.ts b/server/src/interceptors/aclFilterResponse.interceptor.ts
new file mode 100644
index 0000000..5eeba18
--- /dev/null
+++ b/server/src/interceptors/aclFilterResponse.interceptor.ts
@@ -0,0 +1,42 @@
+import {
+ CallHandler,
+ ExecutionContext,
+ Injectable,
+ NestInterceptor,
+} from "@nestjs/common";
+import { Observable } from "rxjs";
+import { map } from "rxjs/operators";
+import { InjectRolesBuilder, RolesBuilder } from "nest-access-control";
+import { Reflector } from "@nestjs/core";
+
+@Injectable()
+export class AclFilterResponseInterceptor implements NestInterceptor {
+ constructor(
+ @InjectRolesBuilder() private readonly rolesBuilder: RolesBuilder,
+ private readonly reflector: Reflector
+ ) {}
+
+ intercept(context: ExecutionContext, next: CallHandler): Observable {
+ const [permissionsRoles]: any = this.reflector.getAllAndMerge(
+ "roles",
+ [context.getHandler(), context.getClass()]
+ );
+
+ const permission = this.rolesBuilder.permission({
+ role: permissionsRoles.role,
+ action: permissionsRoles.action,
+ possession: permissionsRoles.possession,
+ resource: permissionsRoles.resource,
+ });
+
+ return next.handle().pipe(
+ map((data) => {
+ if (Array.isArray(data)) {
+ return data.map((results: any) => permission.filter(results));
+ } else {
+ return permission.filter(data);
+ }
+ })
+ );
+ }
+}
diff --git a/server/src/interceptors/aclValidateRequest.interceptor.ts b/server/src/interceptors/aclValidateRequest.interceptor.ts
new file mode 100644
index 0000000..6d30246
--- /dev/null
+++ b/server/src/interceptors/aclValidateRequest.interceptor.ts
@@ -0,0 +1,53 @@
+import {
+ CallHandler,
+ ExecutionContext,
+ Injectable,
+ NestInterceptor,
+} from "@nestjs/common";
+import { Observable } from "rxjs";
+import { InjectRolesBuilder, RolesBuilder } from "nest-access-control";
+import { Reflector } from "@nestjs/core";
+import * as abacUtil from "../auth/abac.util";
+import { ForbiddenException } from "../errors";
+
+@Injectable()
+export class AclValidateRequestInterceptor implements NestInterceptor {
+ constructor(
+ @InjectRolesBuilder() private readonly rolesBuilder: RolesBuilder,
+ private readonly reflector: Reflector
+ ) {}
+
+ intercept(context: ExecutionContext, next: CallHandler): Observable {
+ const [permissionsRoles]: any = this.reflector.getAllAndMerge(
+ "roles",
+ [context.getHandler(), context.getClass()]
+ );
+
+ const type = context.getType();
+
+ const inputDataToValidate =
+ type === "http"
+ ? context.switchToHttp().getRequest().body
+ : context.getArgByIndex(1).data;
+
+ const permission = this.rolesBuilder.permission({
+ role: permissionsRoles.role,
+ action: permissionsRoles.action,
+ possession: permissionsRoles.possession,
+ resource: permissionsRoles.resource,
+ });
+
+ const invalidAttributes = abacUtil.getInvalidAttributes(
+ permission,
+ inputDataToValidate
+ );
+
+ if (invalidAttributes.length) {
+ throw new ForbiddenException(
+ "Insufficient privileges to complete the operation"
+ );
+ }
+
+ return next.handle();
+ }
+}
diff --git a/server/src/main.ts b/server/src/main.ts
new file mode 100644
index 0000000..13acf98
--- /dev/null
+++ b/server/src/main.ts
@@ -0,0 +1,48 @@
+import { ValidationPipe } from "@nestjs/common";
+import { NestFactory } from "@nestjs/core";
+import { OpenAPIObject, SwaggerModule } from "@nestjs/swagger";
+// @ts-ignore
+// eslint-disable-next-line
+import { AppModule } from "./app.module";
+import {
+ swaggerPath,
+ swaggerDocumentOptions,
+ swaggerSetupOptions,
+ // @ts-ignore
+ // eslint-disable-next-line
+} from "./swagger";
+
+const { PORT = 3000 } = process.env;
+
+async function main() {
+ const app = await NestFactory.create(AppModule, { cors: true });
+
+ app.setGlobalPrefix("api");
+ app.useGlobalPipes(
+ new ValidationPipe({
+ transform: true,
+ })
+ );
+
+ const document = SwaggerModule.createDocument(app, swaggerDocumentOptions);
+
+ /** check if there is Public decorator for each path (action) and its method (findMany / findOne) on each controller */
+ Object.values((document as OpenAPIObject).paths).forEach((path: any) => {
+ Object.values(path).forEach((method: any) => {
+ if (
+ Array.isArray(method.security) &&
+ method.security.includes("isPublic")
+ ) {
+ method.security = [];
+ }
+ });
+ });
+
+ SwaggerModule.setup(swaggerPath, app, document, swaggerSetupOptions);
+
+ void app.listen(PORT);
+
+ return app;
+}
+
+module.exports = main();
diff --git a/server/src/order/base/CreateOrderArgs.ts b/server/src/order/base/CreateOrderArgs.ts
new file mode 100644
index 0000000..d226b74
--- /dev/null
+++ b/server/src/order/base/CreateOrderArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { OrderCreateInput } from "./OrderCreateInput";
+
+@ArgsType()
+class CreateOrderArgs {
+ @Field(() => OrderCreateInput, { nullable: false })
+ data!: OrderCreateInput;
+}
+
+export { CreateOrderArgs };
diff --git a/server/src/order/base/DeleteOrderArgs.ts b/server/src/order/base/DeleteOrderArgs.ts
new file mode 100644
index 0000000..7d86315
--- /dev/null
+++ b/server/src/order/base/DeleteOrderArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { OrderWhereUniqueInput } from "./OrderWhereUniqueInput";
+
+@ArgsType()
+class DeleteOrderArgs {
+ @Field(() => OrderWhereUniqueInput, { nullable: false })
+ where!: OrderWhereUniqueInput;
+}
+
+export { DeleteOrderArgs };
diff --git a/server/src/order/base/Order.ts b/server/src/order/base/Order.ts
new file mode 100644
index 0000000..56ff9e4
--- /dev/null
+++ b/server/src/order/base/Order.ts
@@ -0,0 +1,102 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ObjectType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import {
+ IsDate,
+ ValidateNested,
+ IsOptional,
+ IsNumber,
+ IsString,
+ IsInt,
+} from "class-validator";
+import { Type } from "class-transformer";
+import { Customer } from "../../customer/base/Customer";
+import { Product } from "../../product/base/Product";
+@ObjectType()
+class Order {
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ createdAt!: Date;
+
+ @ApiProperty({
+ required: false,
+ type: () => Customer,
+ })
+ @ValidateNested()
+ @Type(() => Customer)
+ @IsOptional()
+ customer?: Customer | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsNumber()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ discount!: number | null;
+
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+
+ @ApiProperty({
+ required: false,
+ type: () => Product,
+ })
+ @ValidateNested()
+ @Type(() => Product)
+ @IsOptional()
+ product?: Product | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsInt()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ quantity!: number | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsInt()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ totalPrice!: number | null;
+
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ updatedAt!: Date;
+}
+export { Order };
diff --git a/server/src/order/base/OrderCreateInput.ts b/server/src/order/base/OrderCreateInput.ts
new file mode 100644
index 0000000..f598f0c
--- /dev/null
+++ b/server/src/order/base/OrderCreateInput.ts
@@ -0,0 +1,77 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { CustomerWhereUniqueInput } from "../../customer/base/CustomerWhereUniqueInput";
+import { ValidateNested, IsOptional, IsNumber, IsInt } from "class-validator";
+import { Type } from "class-transformer";
+import { ProductWhereUniqueInput } from "../../product/base/ProductWhereUniqueInput";
+@InputType()
+class OrderCreateInput {
+ @ApiProperty({
+ required: false,
+ type: () => CustomerWhereUniqueInput,
+ })
+ @ValidateNested()
+ @Type(() => CustomerWhereUniqueInput)
+ @IsOptional()
+ @Field(() => CustomerWhereUniqueInput, {
+ nullable: true,
+ })
+ customer?: CustomerWhereUniqueInput | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsNumber()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ discount?: number | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => ProductWhereUniqueInput,
+ })
+ @ValidateNested()
+ @Type(() => ProductWhereUniqueInput)
+ @IsOptional()
+ @Field(() => ProductWhereUniqueInput, {
+ nullable: true,
+ })
+ product?: ProductWhereUniqueInput | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsInt()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ quantity?: number | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsInt()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ totalPrice?: number | null;
+}
+export { OrderCreateInput };
diff --git a/server/src/order/base/OrderFindManyArgs.ts b/server/src/order/base/OrderFindManyArgs.ts
new file mode 100644
index 0000000..9ae76e4
--- /dev/null
+++ b/server/src/order/base/OrderFindManyArgs.ts
@@ -0,0 +1,53 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { OrderWhereInput } from "./OrderWhereInput";
+import { Type } from "class-transformer";
+import { OrderOrderByInput } from "./OrderOrderByInput";
+
+@ArgsType()
+class OrderFindManyArgs {
+ @ApiProperty({
+ required: false,
+ type: () => OrderWhereInput,
+ })
+ @Field(() => OrderWhereInput, { nullable: true })
+ @Type(() => OrderWhereInput)
+ where?: OrderWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: [OrderOrderByInput],
+ })
+ @Field(() => [OrderOrderByInput], { nullable: true })
+ @Type(() => OrderOrderByInput)
+ orderBy?: Array;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ skip?: number;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ take?: number;
+}
+
+export { OrderFindManyArgs };
diff --git a/server/src/order/base/OrderFindUniqueArgs.ts b/server/src/order/base/OrderFindUniqueArgs.ts
new file mode 100644
index 0000000..1aa4a01
--- /dev/null
+++ b/server/src/order/base/OrderFindUniqueArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { OrderWhereUniqueInput } from "./OrderWhereUniqueInput";
+
+@ArgsType()
+class OrderFindUniqueArgs {
+ @Field(() => OrderWhereUniqueInput, { nullable: false })
+ where!: OrderWhereUniqueInput;
+}
+
+export { OrderFindUniqueArgs };
diff --git a/server/src/order/base/OrderListRelationFilter.ts b/server/src/order/base/OrderListRelationFilter.ts
new file mode 100644
index 0000000..87defd5
--- /dev/null
+++ b/server/src/order/base/OrderListRelationFilter.ts
@@ -0,0 +1,56 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { OrderWhereInput } from "./OrderWhereInput";
+import { ValidateNested, IsOptional } from "class-validator";
+import { Type } from "class-transformer";
+
+@InputType()
+class OrderListRelationFilter {
+ @ApiProperty({
+ required: false,
+ type: () => OrderWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => OrderWhereInput)
+ @IsOptional()
+ @Field(() => OrderWhereInput, {
+ nullable: true,
+ })
+ every?: OrderWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => OrderWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => OrderWhereInput)
+ @IsOptional()
+ @Field(() => OrderWhereInput, {
+ nullable: true,
+ })
+ some?: OrderWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => OrderWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => OrderWhereInput)
+ @IsOptional()
+ @Field(() => OrderWhereInput, {
+ nullable: true,
+ })
+ none?: OrderWhereInput;
+}
+export { OrderListRelationFilter };
diff --git a/server/src/order/base/OrderOrderByInput.ts b/server/src/order/base/OrderOrderByInput.ts
new file mode 100644
index 0000000..a6c2267
--- /dev/null
+++ b/server/src/order/base/OrderOrderByInput.ts
@@ -0,0 +1,94 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { SortOrder } from "../../util/SortOrder";
+
+@InputType({
+ isAbstract: true,
+ description: undefined,
+})
+class OrderOrderByInput {
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ createdAt?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ customerId?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ discount?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ id?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ productId?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ quantity?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ totalPrice?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ updatedAt?: SortOrder;
+}
+
+export { OrderOrderByInput };
diff --git a/server/src/order/base/OrderUpdateInput.ts b/server/src/order/base/OrderUpdateInput.ts
new file mode 100644
index 0000000..57d690d
--- /dev/null
+++ b/server/src/order/base/OrderUpdateInput.ts
@@ -0,0 +1,77 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { CustomerWhereUniqueInput } from "../../customer/base/CustomerWhereUniqueInput";
+import { ValidateNested, IsOptional, IsNumber, IsInt } from "class-validator";
+import { Type } from "class-transformer";
+import { ProductWhereUniqueInput } from "../../product/base/ProductWhereUniqueInput";
+@InputType()
+class OrderUpdateInput {
+ @ApiProperty({
+ required: false,
+ type: () => CustomerWhereUniqueInput,
+ })
+ @ValidateNested()
+ @Type(() => CustomerWhereUniqueInput)
+ @IsOptional()
+ @Field(() => CustomerWhereUniqueInput, {
+ nullable: true,
+ })
+ customer?: CustomerWhereUniqueInput | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsNumber()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ discount?: number | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => ProductWhereUniqueInput,
+ })
+ @ValidateNested()
+ @Type(() => ProductWhereUniqueInput)
+ @IsOptional()
+ @Field(() => ProductWhereUniqueInput, {
+ nullable: true,
+ })
+ product?: ProductWhereUniqueInput | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsInt()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ quantity?: number | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsInt()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ totalPrice?: number | null;
+}
+export { OrderUpdateInput };
diff --git a/server/src/order/base/OrderWhereInput.ts b/server/src/order/base/OrderWhereInput.ts
new file mode 100644
index 0000000..588cdf2
--- /dev/null
+++ b/server/src/order/base/OrderWhereInput.ts
@@ -0,0 +1,91 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { CustomerWhereUniqueInput } from "../../customer/base/CustomerWhereUniqueInput";
+import { ValidateNested, IsOptional } from "class-validator";
+import { Type } from "class-transformer";
+import { FloatNullableFilter } from "../../util/FloatNullableFilter";
+import { StringFilter } from "../../util/StringFilter";
+import { ProductWhereUniqueInput } from "../../product/base/ProductWhereUniqueInput";
+import { IntNullableFilter } from "../../util/IntNullableFilter";
+@InputType()
+class OrderWhereInput {
+ @ApiProperty({
+ required: false,
+ type: () => CustomerWhereUniqueInput,
+ })
+ @ValidateNested()
+ @Type(() => CustomerWhereUniqueInput)
+ @IsOptional()
+ @Field(() => CustomerWhereUniqueInput, {
+ nullable: true,
+ })
+ customer?: CustomerWhereUniqueInput;
+
+ @ApiProperty({
+ required: false,
+ type: FloatNullableFilter,
+ })
+ @Type(() => FloatNullableFilter)
+ @IsOptional()
+ @Field(() => FloatNullableFilter, {
+ nullable: true,
+ })
+ discount?: FloatNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringFilter,
+ })
+ @Type(() => StringFilter)
+ @IsOptional()
+ @Field(() => StringFilter, {
+ nullable: true,
+ })
+ id?: StringFilter;
+
+ @ApiProperty({
+ required: false,
+ type: () => ProductWhereUniqueInput,
+ })
+ @ValidateNested()
+ @Type(() => ProductWhereUniqueInput)
+ @IsOptional()
+ @Field(() => ProductWhereUniqueInput, {
+ nullable: true,
+ })
+ product?: ProductWhereUniqueInput;
+
+ @ApiProperty({
+ required: false,
+ type: IntNullableFilter,
+ })
+ @Type(() => IntNullableFilter)
+ @IsOptional()
+ @Field(() => IntNullableFilter, {
+ nullable: true,
+ })
+ quantity?: IntNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: IntNullableFilter,
+ })
+ @Type(() => IntNullableFilter)
+ @IsOptional()
+ @Field(() => IntNullableFilter, {
+ nullable: true,
+ })
+ totalPrice?: IntNullableFilter;
+}
+export { OrderWhereInput };
diff --git a/server/src/order/base/OrderWhereUniqueInput.ts b/server/src/order/base/OrderWhereUniqueInput.ts
new file mode 100644
index 0000000..fc65797
--- /dev/null
+++ b/server/src/order/base/OrderWhereUniqueInput.ts
@@ -0,0 +1,25 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsString } from "class-validator";
+@InputType()
+class OrderWhereUniqueInput {
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+}
+export { OrderWhereUniqueInput };
diff --git a/server/src/order/base/UpdateOrderArgs.ts b/server/src/order/base/UpdateOrderArgs.ts
new file mode 100644
index 0000000..f38a095
--- /dev/null
+++ b/server/src/order/base/UpdateOrderArgs.ts
@@ -0,0 +1,24 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { OrderWhereUniqueInput } from "./OrderWhereUniqueInput";
+import { OrderUpdateInput } from "./OrderUpdateInput";
+
+@ArgsType()
+class UpdateOrderArgs {
+ @Field(() => OrderWhereUniqueInput, { nullable: false })
+ where!: OrderWhereUniqueInput;
+ @Field(() => OrderUpdateInput, { nullable: false })
+ data!: OrderUpdateInput;
+}
+
+export { UpdateOrderArgs };
diff --git a/server/src/order/base/order.controller.base.spec.ts b/server/src/order/base/order.controller.base.spec.ts
new file mode 100644
index 0000000..7b0f6af
--- /dev/null
+++ b/server/src/order/base/order.controller.base.spec.ts
@@ -0,0 +1,181 @@
+import { Test } from "@nestjs/testing";
+import {
+ INestApplication,
+ HttpStatus,
+ ExecutionContext,
+ CallHandler,
+} from "@nestjs/common";
+import request from "supertest";
+import { MorganModule } from "nest-morgan";
+import { ACGuard } from "nest-access-control";
+import { DefaultAuthGuard } from "../../auth/defaultAuth.guard";
+import { ACLModule } from "../../auth/acl.module";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { map } from "rxjs";
+import { OrderController } from "../order.controller";
+import { OrderService } from "../order.service";
+
+const nonExistingId = "nonExistingId";
+const existingId = "existingId";
+const CREATE_INPUT = {
+ createdAt: new Date(),
+ discount: 42.42,
+ id: "exampleId",
+ quantity: 42,
+ totalPrice: 42,
+ updatedAt: new Date(),
+};
+const CREATE_RESULT = {
+ createdAt: new Date(),
+ discount: 42.42,
+ id: "exampleId",
+ quantity: 42,
+ totalPrice: 42,
+ updatedAt: new Date(),
+};
+const FIND_MANY_RESULT = [
+ {
+ createdAt: new Date(),
+ discount: 42.42,
+ id: "exampleId",
+ quantity: 42,
+ totalPrice: 42,
+ updatedAt: new Date(),
+ },
+];
+const FIND_ONE_RESULT = {
+ createdAt: new Date(),
+ discount: 42.42,
+ id: "exampleId",
+ quantity: 42,
+ totalPrice: 42,
+ updatedAt: new Date(),
+};
+
+const service = {
+ create() {
+ return CREATE_RESULT;
+ },
+ findMany: () => FIND_MANY_RESULT,
+ findOne: ({ where }: { where: { id: string } }) => {
+ switch (where.id) {
+ case existingId:
+ return FIND_ONE_RESULT;
+ case nonExistingId:
+ return null;
+ }
+ },
+};
+
+const basicAuthGuard = {
+ canActivate: (context: ExecutionContext) => {
+ const argumentHost = context.switchToHttp();
+ const request = argumentHost.getRequest();
+ request.user = {
+ roles: ["user"],
+ };
+ return true;
+ },
+};
+
+const acGuard = {
+ canActivate: () => {
+ return true;
+ },
+};
+
+const aclFilterResponseInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle().pipe(
+ map((data) => {
+ return data;
+ })
+ );
+ },
+};
+const aclValidateRequestInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle();
+ },
+};
+
+describe("Order", () => {
+ let app: INestApplication;
+
+ beforeAll(async () => {
+ const moduleRef = await Test.createTestingModule({
+ providers: [
+ {
+ provide: OrderService,
+ useValue: service,
+ },
+ ],
+ controllers: [OrderController],
+ imports: [MorganModule.forRoot(), ACLModule],
+ })
+ .overrideGuard(DefaultAuthGuard)
+ .useValue(basicAuthGuard)
+ .overrideGuard(ACGuard)
+ .useValue(acGuard)
+ .overrideInterceptor(AclFilterResponseInterceptor)
+ .useValue(aclFilterResponseInterceptor)
+ .overrideInterceptor(AclValidateRequestInterceptor)
+ .useValue(aclValidateRequestInterceptor)
+ .compile();
+
+ app = moduleRef.createNestApplication();
+ await app.init();
+ });
+
+ test("POST /orders", async () => {
+ await request(app.getHttpServer())
+ .post("/orders")
+ .send(CREATE_INPUT)
+ .expect(HttpStatus.CREATED)
+ .expect({
+ ...CREATE_RESULT,
+ createdAt: CREATE_RESULT.createdAt.toISOString(),
+ updatedAt: CREATE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ test("GET /orders", async () => {
+ await request(app.getHttpServer())
+ .get("/orders")
+ .expect(HttpStatus.OK)
+ .expect([
+ {
+ ...FIND_MANY_RESULT[0],
+ createdAt: FIND_MANY_RESULT[0].createdAt.toISOString(),
+ updatedAt: FIND_MANY_RESULT[0].updatedAt.toISOString(),
+ },
+ ]);
+ });
+
+ test("GET /orders/:id non existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/orders"}/${nonExistingId}`)
+ .expect(HttpStatus.NOT_FOUND)
+ .expect({
+ statusCode: HttpStatus.NOT_FOUND,
+ message: `No resource was found for {"${"id"}":"${nonExistingId}"}`,
+ error: "Not Found",
+ });
+ });
+
+ test("GET /orders/:id existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/orders"}/${existingId}`)
+ .expect(HttpStatus.OK)
+ .expect({
+ ...FIND_ONE_RESULT,
+ createdAt: FIND_ONE_RESULT.createdAt.toISOString(),
+ updatedAt: FIND_ONE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ afterAll(async () => {
+ await app.close();
+ });
+});
diff --git a/server/src/order/base/order.controller.base.ts b/server/src/order/base/order.controller.base.ts
new file mode 100644
index 0000000..3d3e274
--- /dev/null
+++ b/server/src/order/base/order.controller.base.ts
@@ -0,0 +1,286 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as swagger from "@nestjs/swagger";
+import * as nestAccessControl from "nest-access-control";
+import * as defaultAuthGuard from "../../auth/defaultAuth.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import * as errors from "../../errors";
+import { Request } from "express";
+import { plainToClass } from "class-transformer";
+import { ApiNestedQuery } from "../../decorators/api-nested-query.decorator";
+import { OrderService } from "../order.service";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { OrderCreateInput } from "./OrderCreateInput";
+import { OrderWhereInput } from "./OrderWhereInput";
+import { OrderWhereUniqueInput } from "./OrderWhereUniqueInput";
+import { OrderFindManyArgs } from "./OrderFindManyArgs";
+import { OrderUpdateInput } from "./OrderUpdateInput";
+import { Order } from "./Order";
+@swagger.ApiBearerAuth()
+@common.UseGuards(defaultAuthGuard.DefaultAuthGuard, nestAccessControl.ACGuard)
+export class OrderControllerBase {
+ constructor(
+ protected readonly service: OrderService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "create",
+ possession: "any",
+ })
+ @common.Post()
+ @swagger.ApiCreatedResponse({ type: Order })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async create(@common.Body() data: OrderCreateInput): Promise {
+ return await this.service.create({
+ data: {
+ ...data,
+
+ customer: data.customer
+ ? {
+ connect: data.customer,
+ }
+ : undefined,
+
+ product: data.product
+ ? {
+ connect: data.product,
+ }
+ : undefined,
+ },
+ select: {
+ createdAt: true,
+
+ customer: {
+ select: {
+ id: true,
+ },
+ },
+
+ discount: true,
+ id: true,
+
+ product: {
+ select: {
+ id: true,
+ },
+ },
+
+ quantity: true,
+ totalPrice: true,
+ updatedAt: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "read",
+ possession: "any",
+ })
+ @common.Get()
+ @swagger.ApiOkResponse({ type: [Order] })
+ @swagger.ApiForbiddenResponse()
+ @ApiNestedQuery(OrderFindManyArgs)
+ async findMany(@common.Req() request: Request): Promise {
+ const args = plainToClass(OrderFindManyArgs, request.query);
+ return this.service.findMany({
+ ...args,
+ select: {
+ createdAt: true,
+
+ customer: {
+ select: {
+ id: true,
+ },
+ },
+
+ discount: true,
+ id: true,
+
+ product: {
+ select: {
+ id: true,
+ },
+ },
+
+ quantity: true,
+ totalPrice: true,
+ updatedAt: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "read",
+ possession: "own",
+ })
+ @common.Get("/:id")
+ @swagger.ApiOkResponse({ type: Order })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async findOne(
+ @common.Param() params: OrderWhereUniqueInput
+ ): Promise {
+ const result = await this.service.findOne({
+ where: params,
+ select: {
+ createdAt: true,
+
+ customer: {
+ select: {
+ id: true,
+ },
+ },
+
+ discount: true,
+ id: true,
+
+ product: {
+ select: {
+ id: true,
+ },
+ },
+
+ quantity: true,
+ totalPrice: true,
+ updatedAt: true,
+ },
+ });
+ if (result === null) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "update",
+ possession: "any",
+ })
+ @common.Patch("/:id")
+ @swagger.ApiOkResponse({ type: Order })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async update(
+ @common.Param() params: OrderWhereUniqueInput,
+ @common.Body() data: OrderUpdateInput
+ ): Promise {
+ try {
+ return await this.service.update({
+ where: params,
+ data: {
+ ...data,
+
+ customer: data.customer
+ ? {
+ connect: data.customer,
+ }
+ : undefined,
+
+ product: data.product
+ ? {
+ connect: data.product,
+ }
+ : undefined,
+ },
+ select: {
+ createdAt: true,
+
+ customer: {
+ select: {
+ id: true,
+ },
+ },
+
+ discount: true,
+ id: true,
+
+ product: {
+ select: {
+ id: true,
+ },
+ },
+
+ quantity: true,
+ totalPrice: true,
+ updatedAt: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "delete",
+ possession: "any",
+ })
+ @common.Delete("/:id")
+ @swagger.ApiOkResponse({ type: Order })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async delete(
+ @common.Param() params: OrderWhereUniqueInput
+ ): Promise {
+ try {
+ return await this.service.delete({
+ where: params,
+ select: {
+ createdAt: true,
+
+ customer: {
+ select: {
+ id: true,
+ },
+ },
+
+ discount: true,
+ id: true,
+
+ product: {
+ select: {
+ id: true,
+ },
+ },
+
+ quantity: true,
+ totalPrice: true,
+ updatedAt: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+}
diff --git a/server/src/order/base/order.module.base.ts b/server/src/order/base/order.module.base.ts
new file mode 100644
index 0000000..7c6844a
--- /dev/null
+++ b/server/src/order/base/order.module.base.ts
@@ -0,0 +1,28 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { Module, forwardRef } from "@nestjs/common";
+import { MorganModule } from "nest-morgan";
+import { PrismaModule } from "nestjs-prisma";
+import { ACLModule } from "../../auth/acl.module";
+import { AuthModule } from "../../auth/auth.module";
+
+@Module({
+ imports: [
+ ACLModule,
+ forwardRef(() => AuthModule),
+ MorganModule,
+ PrismaModule,
+ ],
+
+ exports: [ACLModule, AuthModule, MorganModule, PrismaModule],
+})
+export class OrderModuleBase {}
diff --git a/server/src/order/base/order.resolver.base.ts b/server/src/order/base/order.resolver.base.ts
new file mode 100644
index 0000000..b3328dc
--- /dev/null
+++ b/server/src/order/base/order.resolver.base.ts
@@ -0,0 +1,206 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as graphql from "@nestjs/graphql";
+import * as apollo from "apollo-server-express";
+import * as nestAccessControl from "nest-access-control";
+import { GqlDefaultAuthGuard } from "../../auth/gqlDefaultAuth.guard";
+import * as gqlACGuard from "../../auth/gqlAC.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import { MetaQueryPayload } from "../../util/MetaQueryPayload";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { CreateOrderArgs } from "./CreateOrderArgs";
+import { UpdateOrderArgs } from "./UpdateOrderArgs";
+import { DeleteOrderArgs } from "./DeleteOrderArgs";
+import { OrderFindManyArgs } from "./OrderFindManyArgs";
+import { OrderFindUniqueArgs } from "./OrderFindUniqueArgs";
+import { Order } from "./Order";
+import { Customer } from "../../customer/base/Customer";
+import { Product } from "../../product/base/Product";
+import { OrderService } from "../order.service";
+
+@graphql.Resolver(() => Order)
+@common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+export class OrderResolverBase {
+ constructor(
+ protected readonly service: OrderService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @graphql.Query(() => MetaQueryPayload)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "read",
+ possession: "any",
+ })
+ async _ordersMeta(
+ @graphql.Args() args: OrderFindManyArgs
+ ): Promise {
+ const results = await this.service.count({
+ ...args,
+ skip: undefined,
+ take: undefined,
+ });
+ return {
+ count: results,
+ };
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => [Order])
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "read",
+ possession: "any",
+ })
+ async orders(@graphql.Args() args: OrderFindManyArgs): Promise {
+ return this.service.findMany(args);
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => Order, { nullable: true })
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "read",
+ possession: "own",
+ })
+ async order(
+ @graphql.Args() args: OrderFindUniqueArgs
+ ): Promise {
+ const result = await this.service.findOne(args);
+ if (result === null) {
+ return null;
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => Order)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "create",
+ possession: "any",
+ })
+ async createOrder(@graphql.Args() args: CreateOrderArgs): Promise {
+ return await this.service.create({
+ ...args,
+ data: {
+ ...args.data,
+
+ customer: args.data.customer
+ ? {
+ connect: args.data.customer,
+ }
+ : undefined,
+
+ product: args.data.product
+ ? {
+ connect: args.data.product,
+ }
+ : undefined,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => Order)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "update",
+ possession: "any",
+ })
+ async updateOrder(
+ @graphql.Args() args: UpdateOrderArgs
+ ): Promise {
+ try {
+ return await this.service.update({
+ ...args,
+ data: {
+ ...args.data,
+
+ customer: args.data.customer
+ ? {
+ connect: args.data.customer,
+ }
+ : undefined,
+
+ product: args.data.product
+ ? {
+ connect: args.data.product,
+ }
+ : undefined,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @graphql.Mutation(() => Order)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "delete",
+ possession: "any",
+ })
+ async deleteOrder(
+ @graphql.Args() args: DeleteOrderArgs
+ ): Promise {
+ try {
+ return await this.service.delete(args);
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.ResolveField(() => Customer, { nullable: true })
+ @nestAccessControl.UseRoles({
+ resource: "Customer",
+ action: "read",
+ possession: "any",
+ })
+ async customer(@graphql.Parent() parent: Order): Promise {
+ const result = await this.service.getCustomer(parent.id);
+
+ if (!result) {
+ return null;
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.ResolveField(() => Product, { nullable: true })
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "read",
+ possession: "any",
+ })
+ async product(@graphql.Parent() parent: Order): Promise {
+ const result = await this.service.getProduct(parent.id);
+
+ if (!result) {
+ return null;
+ }
+ return result;
+ }
+}
diff --git a/server/src/order/base/order.service.base.ts b/server/src/order/base/order.service.base.ts
new file mode 100644
index 0000000..22a022d
--- /dev/null
+++ b/server/src/order/base/order.service.base.ts
@@ -0,0 +1,65 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { PrismaService } from "nestjs-prisma";
+import { Prisma, Order, Customer, Product } from "@prisma/client";
+
+export class OrderServiceBase {
+ constructor(protected readonly prisma: PrismaService) {}
+
+ async count(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.order.count(args);
+ }
+
+ async findMany(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.order.findMany(args);
+ }
+ async findOne(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.order.findUnique(args);
+ }
+ async create(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.order.create(args);
+ }
+ async update(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.order.update(args);
+ }
+ async delete(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.order.delete(args);
+ }
+
+ async getCustomer(parentId: string): Promise {
+ return this.prisma.order
+ .findUnique({
+ where: { id: parentId },
+ })
+ .customer();
+ }
+
+ async getProduct(parentId: string): Promise {
+ return this.prisma.order
+ .findUnique({
+ where: { id: parentId },
+ })
+ .product();
+ }
+}
diff --git a/server/src/order/order.controller.ts b/server/src/order/order.controller.ts
new file mode 100644
index 0000000..07002fd
--- /dev/null
+++ b/server/src/order/order.controller.ts
@@ -0,0 +1,17 @@
+import * as common from "@nestjs/common";
+import * as swagger from "@nestjs/swagger";
+import * as nestAccessControl from "nest-access-control";
+import { OrderService } from "./order.service";
+import { OrderControllerBase } from "./base/order.controller.base";
+
+@swagger.ApiTags("orders")
+@common.Controller("orders")
+export class OrderController extends OrderControllerBase {
+ constructor(
+ protected readonly service: OrderService,
+ @nestAccessControl.InjectRolesBuilder()
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {
+ super(service, rolesBuilder);
+ }
+}
diff --git a/server/src/order/order.module.ts b/server/src/order/order.module.ts
new file mode 100644
index 0000000..dc23d79
--- /dev/null
+++ b/server/src/order/order.module.ts
@@ -0,0 +1,13 @@
+import { Module } from "@nestjs/common";
+import { OrderModuleBase } from "./base/order.module.base";
+import { OrderService } from "./order.service";
+import { OrderController } from "./order.controller";
+import { OrderResolver } from "./order.resolver";
+
+@Module({
+ imports: [OrderModuleBase],
+ controllers: [OrderController],
+ providers: [OrderService, OrderResolver],
+ exports: [OrderService],
+})
+export class OrderModule {}
diff --git a/server/src/order/order.resolver.ts b/server/src/order/order.resolver.ts
new file mode 100644
index 0000000..bea989c
--- /dev/null
+++ b/server/src/order/order.resolver.ts
@@ -0,0 +1,20 @@
+import * as common from "@nestjs/common";
+import * as graphql from "@nestjs/graphql";
+import * as nestAccessControl from "nest-access-control";
+import { GqlDefaultAuthGuard } from "../auth/gqlDefaultAuth.guard";
+import * as gqlACGuard from "../auth/gqlAC.guard";
+import { OrderResolverBase } from "./base/order.resolver.base";
+import { Order } from "./base/Order";
+import { OrderService } from "./order.service";
+
+@graphql.Resolver(() => Order)
+@common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+export class OrderResolver extends OrderResolverBase {
+ constructor(
+ protected readonly service: OrderService,
+ @nestAccessControl.InjectRolesBuilder()
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {
+ super(service, rolesBuilder);
+ }
+}
diff --git a/server/src/order/order.service.ts b/server/src/order/order.service.ts
new file mode 100644
index 0000000..600fa24
--- /dev/null
+++ b/server/src/order/order.service.ts
@@ -0,0 +1,10 @@
+import { Injectable } from "@nestjs/common";
+import { PrismaService } from "nestjs-prisma";
+import { OrderServiceBase } from "./base/order.service.base";
+
+@Injectable()
+export class OrderService extends OrderServiceBase {
+ constructor(protected readonly prisma: PrismaService) {
+ super(prisma);
+ }
+}
diff --git a/server/src/prisma.util.ts b/server/src/prisma.util.ts
new file mode 100644
index 0000000..8e0779c
--- /dev/null
+++ b/server/src/prisma.util.ts
@@ -0,0 +1,30 @@
+export const PRISMA_QUERY_INTERPRETATION_ERROR = "P2016";
+export const PRISMA_RECORD_NOT_FOUND = "RecordNotFound";
+
+export function isRecordNotFoundError(
+ error: Error & { code?: string }
+): boolean {
+ return (
+ "code" in error &&
+ error.code === PRISMA_QUERY_INTERPRETATION_ERROR &&
+ error.message.includes(PRISMA_RECORD_NOT_FOUND)
+ );
+}
+
+export async function transformStringFieldUpdateInput<
+ T extends undefined | string | { set?: string }
+>(input: T, transform: (input: string) => Promise): Promise {
+ if (typeof input === "object" && typeof input?.set === "string") {
+ return { set: await transform(input.set) } as T;
+ }
+ if (typeof input === "object") {
+ if (typeof input.set === "string") {
+ return { set: await transform(input.set) } as T;
+ }
+ return input;
+ }
+ if (typeof input === "string") {
+ return (await transform(input)) as T;
+ }
+ return input;
+}
diff --git a/server/src/product/base/CreateProductArgs.ts b/server/src/product/base/CreateProductArgs.ts
new file mode 100644
index 0000000..6608450
--- /dev/null
+++ b/server/src/product/base/CreateProductArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { ProductCreateInput } from "./ProductCreateInput";
+
+@ArgsType()
+class CreateProductArgs {
+ @Field(() => ProductCreateInput, { nullable: false })
+ data!: ProductCreateInput;
+}
+
+export { CreateProductArgs };
diff --git a/server/src/product/base/DeleteProductArgs.ts b/server/src/product/base/DeleteProductArgs.ts
new file mode 100644
index 0000000..08659f5
--- /dev/null
+++ b/server/src/product/base/DeleteProductArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { ProductWhereUniqueInput } from "./ProductWhereUniqueInput";
+
+@ArgsType()
+class DeleteProductArgs {
+ @Field(() => ProductWhereUniqueInput, { nullable: false })
+ where!: ProductWhereUniqueInput;
+}
+
+export { DeleteProductArgs };
diff --git a/server/src/product/base/OrderCreateNestedManyWithoutProductsInput.ts b/server/src/product/base/OrderCreateNestedManyWithoutProductsInput.ts
new file mode 100644
index 0000000..5cf7be8
--- /dev/null
+++ b/server/src/product/base/OrderCreateNestedManyWithoutProductsInput.ts
@@ -0,0 +1,26 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { OrderWhereUniqueInput } from "../../order/base/OrderWhereUniqueInput";
+import { ApiProperty } from "@nestjs/swagger";
+@InputType()
+class OrderCreateNestedManyWithoutProductsInput {
+ @Field(() => [OrderWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [OrderWhereUniqueInput],
+ })
+ connect?: Array;
+}
+export { OrderCreateNestedManyWithoutProductsInput };
diff --git a/server/src/product/base/OrderUpdateManyWithoutProductsInput.ts b/server/src/product/base/OrderUpdateManyWithoutProductsInput.ts
new file mode 100644
index 0000000..85d6d8f
--- /dev/null
+++ b/server/src/product/base/OrderUpdateManyWithoutProductsInput.ts
@@ -0,0 +1,44 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { OrderWhereUniqueInput } from "../../order/base/OrderWhereUniqueInput";
+import { ApiProperty } from "@nestjs/swagger";
+@InputType()
+class OrderUpdateManyWithoutProductsInput {
+ @Field(() => [OrderWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [OrderWhereUniqueInput],
+ })
+ connect?: Array;
+
+ @Field(() => [OrderWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [OrderWhereUniqueInput],
+ })
+ disconnect?: Array;
+
+ @Field(() => [OrderWhereUniqueInput], {
+ nullable: true,
+ })
+ @ApiProperty({
+ required: false,
+ type: () => [OrderWhereUniqueInput],
+ })
+ set?: Array;
+}
+export { OrderUpdateManyWithoutProductsInput };
diff --git a/server/src/product/base/Product.ts b/server/src/product/base/Product.ts
new file mode 100644
index 0000000..065290e
--- /dev/null
+++ b/server/src/product/base/Product.ts
@@ -0,0 +1,91 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ObjectType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import {
+ IsDate,
+ IsString,
+ IsOptional,
+ IsNumber,
+ ValidateNested,
+} from "class-validator";
+import { Type } from "class-transformer";
+import { Order } from "../../order/base/Order";
+@ObjectType()
+class Product {
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ createdAt!: Date;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ description!: string | null;
+
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsNumber()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ itemPrice!: number | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ name!: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => [Order],
+ })
+ @ValidateNested()
+ @Type(() => Order)
+ @IsOptional()
+ orders?: Array;
+
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ updatedAt!: Date;
+}
+export { Product };
diff --git a/server/src/product/base/ProductCreateInput.ts b/server/src/product/base/ProductCreateInput.ts
new file mode 100644
index 0000000..dd0d224
--- /dev/null
+++ b/server/src/product/base/ProductCreateInput.ts
@@ -0,0 +1,69 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import {
+ IsString,
+ IsOptional,
+ IsNumber,
+ ValidateNested,
+} from "class-validator";
+import { OrderCreateNestedManyWithoutProductsInput } from "./OrderCreateNestedManyWithoutProductsInput";
+import { Type } from "class-transformer";
+@InputType()
+class ProductCreateInput {
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ description?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsNumber()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ itemPrice?: number | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ name?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => OrderCreateNestedManyWithoutProductsInput,
+ })
+ @ValidateNested()
+ @Type(() => OrderCreateNestedManyWithoutProductsInput)
+ @IsOptional()
+ @Field(() => OrderCreateNestedManyWithoutProductsInput, {
+ nullable: true,
+ })
+ orders?: OrderCreateNestedManyWithoutProductsInput;
+}
+export { ProductCreateInput };
diff --git a/server/src/product/base/ProductFindManyArgs.ts b/server/src/product/base/ProductFindManyArgs.ts
new file mode 100644
index 0000000..7efb0e8
--- /dev/null
+++ b/server/src/product/base/ProductFindManyArgs.ts
@@ -0,0 +1,53 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { ProductWhereInput } from "./ProductWhereInput";
+import { Type } from "class-transformer";
+import { ProductOrderByInput } from "./ProductOrderByInput";
+
+@ArgsType()
+class ProductFindManyArgs {
+ @ApiProperty({
+ required: false,
+ type: () => ProductWhereInput,
+ })
+ @Field(() => ProductWhereInput, { nullable: true })
+ @Type(() => ProductWhereInput)
+ where?: ProductWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: [ProductOrderByInput],
+ })
+ @Field(() => [ProductOrderByInput], { nullable: true })
+ @Type(() => ProductOrderByInput)
+ orderBy?: Array;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ skip?: number;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ take?: number;
+}
+
+export { ProductFindManyArgs };
diff --git a/server/src/product/base/ProductFindUniqueArgs.ts b/server/src/product/base/ProductFindUniqueArgs.ts
new file mode 100644
index 0000000..641e3c4
--- /dev/null
+++ b/server/src/product/base/ProductFindUniqueArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { ProductWhereUniqueInput } from "./ProductWhereUniqueInput";
+
+@ArgsType()
+class ProductFindUniqueArgs {
+ @Field(() => ProductWhereUniqueInput, { nullable: false })
+ where!: ProductWhereUniqueInput;
+}
+
+export { ProductFindUniqueArgs };
diff --git a/server/src/product/base/ProductListRelationFilter.ts b/server/src/product/base/ProductListRelationFilter.ts
new file mode 100644
index 0000000..297c938
--- /dev/null
+++ b/server/src/product/base/ProductListRelationFilter.ts
@@ -0,0 +1,56 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { ProductWhereInput } from "./ProductWhereInput";
+import { ValidateNested, IsOptional } from "class-validator";
+import { Type } from "class-transformer";
+
+@InputType()
+class ProductListRelationFilter {
+ @ApiProperty({
+ required: false,
+ type: () => ProductWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => ProductWhereInput)
+ @IsOptional()
+ @Field(() => ProductWhereInput, {
+ nullable: true,
+ })
+ every?: ProductWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => ProductWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => ProductWhereInput)
+ @IsOptional()
+ @Field(() => ProductWhereInput, {
+ nullable: true,
+ })
+ some?: ProductWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => ProductWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => ProductWhereInput)
+ @IsOptional()
+ @Field(() => ProductWhereInput, {
+ nullable: true,
+ })
+ none?: ProductWhereInput;
+}
+export { ProductListRelationFilter };
diff --git a/server/src/product/base/ProductOrderByInput.ts b/server/src/product/base/ProductOrderByInput.ts
new file mode 100644
index 0000000..3110692
--- /dev/null
+++ b/server/src/product/base/ProductOrderByInput.ts
@@ -0,0 +1,76 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { SortOrder } from "../../util/SortOrder";
+
+@InputType({
+ isAbstract: true,
+ description: undefined,
+})
+class ProductOrderByInput {
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ createdAt?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ description?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ id?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ itemPrice?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ name?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ updatedAt?: SortOrder;
+}
+
+export { ProductOrderByInput };
diff --git a/server/src/product/base/ProductUpdateInput.ts b/server/src/product/base/ProductUpdateInput.ts
new file mode 100644
index 0000000..b1559eb
--- /dev/null
+++ b/server/src/product/base/ProductUpdateInput.ts
@@ -0,0 +1,69 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import {
+ IsString,
+ IsOptional,
+ IsNumber,
+ ValidateNested,
+} from "class-validator";
+import { OrderUpdateManyWithoutProductsInput } from "./OrderUpdateManyWithoutProductsInput";
+import { Type } from "class-transformer";
+@InputType()
+class ProductUpdateInput {
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ description?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @IsNumber()
+ @IsOptional()
+ @Field(() => Number, {
+ nullable: true,
+ })
+ itemPrice?: number | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ name?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: () => OrderUpdateManyWithoutProductsInput,
+ })
+ @ValidateNested()
+ @Type(() => OrderUpdateManyWithoutProductsInput)
+ @IsOptional()
+ @Field(() => OrderUpdateManyWithoutProductsInput, {
+ nullable: true,
+ })
+ orders?: OrderUpdateManyWithoutProductsInput;
+}
+export { ProductUpdateInput };
diff --git a/server/src/product/base/ProductWhereInput.ts b/server/src/product/base/ProductWhereInput.ts
new file mode 100644
index 0000000..c083f4b
--- /dev/null
+++ b/server/src/product/base/ProductWhereInput.ts
@@ -0,0 +1,78 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { StringNullableFilter } from "../../util/StringNullableFilter";
+import { Type } from "class-transformer";
+import { IsOptional, ValidateNested } from "class-validator";
+import { StringFilter } from "../../util/StringFilter";
+import { FloatNullableFilter } from "../../util/FloatNullableFilter";
+import { OrderListRelationFilter } from "../../order/base/OrderListRelationFilter";
+@InputType()
+class ProductWhereInput {
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ description?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringFilter,
+ })
+ @Type(() => StringFilter)
+ @IsOptional()
+ @Field(() => StringFilter, {
+ nullable: true,
+ })
+ id?: StringFilter;
+
+ @ApiProperty({
+ required: false,
+ type: FloatNullableFilter,
+ })
+ @Type(() => FloatNullableFilter)
+ @IsOptional()
+ @Field(() => FloatNullableFilter, {
+ nullable: true,
+ })
+ itemPrice?: FloatNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ name?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: () => OrderListRelationFilter,
+ })
+ @ValidateNested()
+ @Type(() => OrderListRelationFilter)
+ @IsOptional()
+ @Field(() => OrderListRelationFilter, {
+ nullable: true,
+ })
+ orders?: OrderListRelationFilter;
+}
+export { ProductWhereInput };
diff --git a/server/src/product/base/ProductWhereUniqueInput.ts b/server/src/product/base/ProductWhereUniqueInput.ts
new file mode 100644
index 0000000..e9adddb
--- /dev/null
+++ b/server/src/product/base/ProductWhereUniqueInput.ts
@@ -0,0 +1,25 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsString } from "class-validator";
+@InputType()
+class ProductWhereUniqueInput {
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+}
+export { ProductWhereUniqueInput };
diff --git a/server/src/product/base/UpdateProductArgs.ts b/server/src/product/base/UpdateProductArgs.ts
new file mode 100644
index 0000000..6ade7ad
--- /dev/null
+++ b/server/src/product/base/UpdateProductArgs.ts
@@ -0,0 +1,24 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { ProductWhereUniqueInput } from "./ProductWhereUniqueInput";
+import { ProductUpdateInput } from "./ProductUpdateInput";
+
+@ArgsType()
+class UpdateProductArgs {
+ @Field(() => ProductWhereUniqueInput, { nullable: false })
+ where!: ProductWhereUniqueInput;
+ @Field(() => ProductUpdateInput, { nullable: false })
+ data!: ProductUpdateInput;
+}
+
+export { UpdateProductArgs };
diff --git a/server/src/product/base/product.controller.base.spec.ts b/server/src/product/base/product.controller.base.spec.ts
new file mode 100644
index 0000000..7f62ce0
--- /dev/null
+++ b/server/src/product/base/product.controller.base.spec.ts
@@ -0,0 +1,181 @@
+import { Test } from "@nestjs/testing";
+import {
+ INestApplication,
+ HttpStatus,
+ ExecutionContext,
+ CallHandler,
+} from "@nestjs/common";
+import request from "supertest";
+import { MorganModule } from "nest-morgan";
+import { ACGuard } from "nest-access-control";
+import { DefaultAuthGuard } from "../../auth/defaultAuth.guard";
+import { ACLModule } from "../../auth/acl.module";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { map } from "rxjs";
+import { ProductController } from "../product.controller";
+import { ProductService } from "../product.service";
+
+const nonExistingId = "nonExistingId";
+const existingId = "existingId";
+const CREATE_INPUT = {
+ createdAt: new Date(),
+ description: "exampleDescription",
+ id: "exampleId",
+ itemPrice: 42.42,
+ name: "exampleName",
+ updatedAt: new Date(),
+};
+const CREATE_RESULT = {
+ createdAt: new Date(),
+ description: "exampleDescription",
+ id: "exampleId",
+ itemPrice: 42.42,
+ name: "exampleName",
+ updatedAt: new Date(),
+};
+const FIND_MANY_RESULT = [
+ {
+ createdAt: new Date(),
+ description: "exampleDescription",
+ id: "exampleId",
+ itemPrice: 42.42,
+ name: "exampleName",
+ updatedAt: new Date(),
+ },
+];
+const FIND_ONE_RESULT = {
+ createdAt: new Date(),
+ description: "exampleDescription",
+ id: "exampleId",
+ itemPrice: 42.42,
+ name: "exampleName",
+ updatedAt: new Date(),
+};
+
+const service = {
+ create() {
+ return CREATE_RESULT;
+ },
+ findMany: () => FIND_MANY_RESULT,
+ findOne: ({ where }: { where: { id: string } }) => {
+ switch (where.id) {
+ case existingId:
+ return FIND_ONE_RESULT;
+ case nonExistingId:
+ return null;
+ }
+ },
+};
+
+const basicAuthGuard = {
+ canActivate: (context: ExecutionContext) => {
+ const argumentHost = context.switchToHttp();
+ const request = argumentHost.getRequest();
+ request.user = {
+ roles: ["user"],
+ };
+ return true;
+ },
+};
+
+const acGuard = {
+ canActivate: () => {
+ return true;
+ },
+};
+
+const aclFilterResponseInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle().pipe(
+ map((data) => {
+ return data;
+ })
+ );
+ },
+};
+const aclValidateRequestInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle();
+ },
+};
+
+describe("Product", () => {
+ let app: INestApplication;
+
+ beforeAll(async () => {
+ const moduleRef = await Test.createTestingModule({
+ providers: [
+ {
+ provide: ProductService,
+ useValue: service,
+ },
+ ],
+ controllers: [ProductController],
+ imports: [MorganModule.forRoot(), ACLModule],
+ })
+ .overrideGuard(DefaultAuthGuard)
+ .useValue(basicAuthGuard)
+ .overrideGuard(ACGuard)
+ .useValue(acGuard)
+ .overrideInterceptor(AclFilterResponseInterceptor)
+ .useValue(aclFilterResponseInterceptor)
+ .overrideInterceptor(AclValidateRequestInterceptor)
+ .useValue(aclValidateRequestInterceptor)
+ .compile();
+
+ app = moduleRef.createNestApplication();
+ await app.init();
+ });
+
+ test("POST /products", async () => {
+ await request(app.getHttpServer())
+ .post("/products")
+ .send(CREATE_INPUT)
+ .expect(HttpStatus.CREATED)
+ .expect({
+ ...CREATE_RESULT,
+ createdAt: CREATE_RESULT.createdAt.toISOString(),
+ updatedAt: CREATE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ test("GET /products", async () => {
+ await request(app.getHttpServer())
+ .get("/products")
+ .expect(HttpStatus.OK)
+ .expect([
+ {
+ ...FIND_MANY_RESULT[0],
+ createdAt: FIND_MANY_RESULT[0].createdAt.toISOString(),
+ updatedAt: FIND_MANY_RESULT[0].updatedAt.toISOString(),
+ },
+ ]);
+ });
+
+ test("GET /products/:id non existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/products"}/${nonExistingId}`)
+ .expect(HttpStatus.NOT_FOUND)
+ .expect({
+ statusCode: HttpStatus.NOT_FOUND,
+ message: `No resource was found for {"${"id"}":"${nonExistingId}"}`,
+ error: "Not Found",
+ });
+ });
+
+ test("GET /products/:id existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/products"}/${existingId}`)
+ .expect(HttpStatus.OK)
+ .expect({
+ ...FIND_ONE_RESULT,
+ createdAt: FIND_ONE_RESULT.createdAt.toISOString(),
+ updatedAt: FIND_ONE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ afterAll(async () => {
+ await app.close();
+ });
+});
diff --git a/server/src/product/base/product.controller.base.ts b/server/src/product/base/product.controller.base.ts
new file mode 100644
index 0000000..1af05c4
--- /dev/null
+++ b/server/src/product/base/product.controller.base.ts
@@ -0,0 +1,303 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as swagger from "@nestjs/swagger";
+import * as nestAccessControl from "nest-access-control";
+import * as defaultAuthGuard from "../../auth/defaultAuth.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import * as errors from "../../errors";
+import { Request } from "express";
+import { plainToClass } from "class-transformer";
+import { ApiNestedQuery } from "../../decorators/api-nested-query.decorator";
+import { ProductService } from "../product.service";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { ProductCreateInput } from "./ProductCreateInput";
+import { ProductWhereInput } from "./ProductWhereInput";
+import { ProductWhereUniqueInput } from "./ProductWhereUniqueInput";
+import { ProductFindManyArgs } from "./ProductFindManyArgs";
+import { ProductUpdateInput } from "./ProductUpdateInput";
+import { Product } from "./Product";
+import { OrderFindManyArgs } from "../../order/base/OrderFindManyArgs";
+import { Order } from "../../order/base/Order";
+import { OrderWhereUniqueInput } from "../../order/base/OrderWhereUniqueInput";
+@swagger.ApiBearerAuth()
+@common.UseGuards(defaultAuthGuard.DefaultAuthGuard, nestAccessControl.ACGuard)
+export class ProductControllerBase {
+ constructor(
+ protected readonly service: ProductService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "create",
+ possession: "any",
+ })
+ @common.Post()
+ @swagger.ApiCreatedResponse({ type: Product })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async create(@common.Body() data: ProductCreateInput): Promise {
+ return await this.service.create({
+ data: data,
+ select: {
+ createdAt: true,
+ description: true,
+ id: true,
+ itemPrice: true,
+ name: true,
+ updatedAt: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "read",
+ possession: "any",
+ })
+ @common.Get()
+ @swagger.ApiOkResponse({ type: [Product] })
+ @swagger.ApiForbiddenResponse()
+ @ApiNestedQuery(ProductFindManyArgs)
+ async findMany(@common.Req() request: Request): Promise {
+ const args = plainToClass(ProductFindManyArgs, request.query);
+ return this.service.findMany({
+ ...args,
+ select: {
+ createdAt: true,
+ description: true,
+ id: true,
+ itemPrice: true,
+ name: true,
+ updatedAt: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "read",
+ possession: "own",
+ })
+ @common.Get("/:id")
+ @swagger.ApiOkResponse({ type: Product })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async findOne(
+ @common.Param() params: ProductWhereUniqueInput
+ ): Promise {
+ const result = await this.service.findOne({
+ where: params,
+ select: {
+ createdAt: true,
+ description: true,
+ id: true,
+ itemPrice: true,
+ name: true,
+ updatedAt: true,
+ },
+ });
+ if (result === null) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "update",
+ possession: "any",
+ })
+ @common.Patch("/:id")
+ @swagger.ApiOkResponse({ type: Product })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async update(
+ @common.Param() params: ProductWhereUniqueInput,
+ @common.Body() data: ProductUpdateInput
+ ): Promise {
+ try {
+ return await this.service.update({
+ where: params,
+ data: data,
+ select: {
+ createdAt: true,
+ description: true,
+ id: true,
+ itemPrice: true,
+ name: true,
+ updatedAt: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "delete",
+ possession: "any",
+ })
+ @common.Delete("/:id")
+ @swagger.ApiOkResponse({ type: Product })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async delete(
+ @common.Param() params: ProductWhereUniqueInput
+ ): Promise {
+ try {
+ return await this.service.delete({
+ where: params,
+ select: {
+ createdAt: true,
+ description: true,
+ id: true,
+ itemPrice: true,
+ name: true,
+ updatedAt: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "read",
+ possession: "any",
+ })
+ @common.Get("/:id/orders")
+ @ApiNestedQuery(OrderFindManyArgs)
+ async findManyOrders(
+ @common.Req() request: Request,
+ @common.Param() params: ProductWhereUniqueInput
+ ): Promise {
+ const query = plainToClass(OrderFindManyArgs, request.query);
+ const results = await this.service.findOrders(params.id, {
+ ...query,
+ select: {
+ createdAt: true,
+
+ customer: {
+ select: {
+ id: true,
+ },
+ },
+
+ discount: true,
+ id: true,
+
+ product: {
+ select: {
+ id: true,
+ },
+ },
+
+ quantity: true,
+ totalPrice: true,
+ updatedAt: true,
+ },
+ });
+ if (results === null) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ return results;
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "update",
+ possession: "any",
+ })
+ @common.Post("/:id/orders")
+ async connectOrders(
+ @common.Param() params: ProductWhereUniqueInput,
+ @common.Body() body: OrderWhereUniqueInput[]
+ ): Promise {
+ const data = {
+ orders: {
+ connect: body,
+ },
+ };
+ await this.service.update({
+ where: params,
+ data,
+ select: { id: true },
+ });
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "update",
+ possession: "any",
+ })
+ @common.Patch("/:id/orders")
+ async updateOrders(
+ @common.Param() params: ProductWhereUniqueInput,
+ @common.Body() body: OrderWhereUniqueInput[]
+ ): Promise {
+ const data = {
+ orders: {
+ set: body,
+ },
+ };
+ await this.service.update({
+ where: params,
+ data,
+ select: { id: true },
+ });
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "update",
+ possession: "any",
+ })
+ @common.Delete("/:id/orders")
+ async disconnectOrders(
+ @common.Param() params: ProductWhereUniqueInput,
+ @common.Body() body: OrderWhereUniqueInput[]
+ ): Promise {
+ const data = {
+ orders: {
+ disconnect: body,
+ },
+ };
+ await this.service.update({
+ where: params,
+ data,
+ select: { id: true },
+ });
+ }
+}
diff --git a/server/src/product/base/product.module.base.ts b/server/src/product/base/product.module.base.ts
new file mode 100644
index 0000000..8b27264
--- /dev/null
+++ b/server/src/product/base/product.module.base.ts
@@ -0,0 +1,28 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { Module, forwardRef } from "@nestjs/common";
+import { MorganModule } from "nest-morgan";
+import { PrismaModule } from "nestjs-prisma";
+import { ACLModule } from "../../auth/acl.module";
+import { AuthModule } from "../../auth/auth.module";
+
+@Module({
+ imports: [
+ ACLModule,
+ forwardRef(() => AuthModule),
+ MorganModule,
+ PrismaModule,
+ ],
+
+ exports: [ACLModule, AuthModule, MorganModule, PrismaModule],
+})
+export class ProductModuleBase {}
diff --git a/server/src/product/base/product.resolver.base.ts b/server/src/product/base/product.resolver.base.ts
new file mode 100644
index 0000000..a2bc02c
--- /dev/null
+++ b/server/src/product/base/product.resolver.base.ts
@@ -0,0 +1,170 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as graphql from "@nestjs/graphql";
+import * as apollo from "apollo-server-express";
+import * as nestAccessControl from "nest-access-control";
+import { GqlDefaultAuthGuard } from "../../auth/gqlDefaultAuth.guard";
+import * as gqlACGuard from "../../auth/gqlAC.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import { MetaQueryPayload } from "../../util/MetaQueryPayload";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { CreateProductArgs } from "./CreateProductArgs";
+import { UpdateProductArgs } from "./UpdateProductArgs";
+import { DeleteProductArgs } from "./DeleteProductArgs";
+import { ProductFindManyArgs } from "./ProductFindManyArgs";
+import { ProductFindUniqueArgs } from "./ProductFindUniqueArgs";
+import { Product } from "./Product";
+import { OrderFindManyArgs } from "../../order/base/OrderFindManyArgs";
+import { Order } from "../../order/base/Order";
+import { ProductService } from "../product.service";
+
+@graphql.Resolver(() => Product)
+@common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+export class ProductResolverBase {
+ constructor(
+ protected readonly service: ProductService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @graphql.Query(() => MetaQueryPayload)
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "read",
+ possession: "any",
+ })
+ async _productsMeta(
+ @graphql.Args() args: ProductFindManyArgs
+ ): Promise {
+ const results = await this.service.count({
+ ...args,
+ skip: undefined,
+ take: undefined,
+ });
+ return {
+ count: results,
+ };
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => [Product])
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "read",
+ possession: "any",
+ })
+ async products(
+ @graphql.Args() args: ProductFindManyArgs
+ ): Promise {
+ return this.service.findMany(args);
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => Product, { nullable: true })
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "read",
+ possession: "own",
+ })
+ async product(
+ @graphql.Args() args: ProductFindUniqueArgs
+ ): Promise {
+ const result = await this.service.findOne(args);
+ if (result === null) {
+ return null;
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => Product)
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "create",
+ possession: "any",
+ })
+ async createProduct(
+ @graphql.Args() args: CreateProductArgs
+ ): Promise {
+ return await this.service.create({
+ ...args,
+ data: args.data,
+ });
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => Product)
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "update",
+ possession: "any",
+ })
+ async updateProduct(
+ @graphql.Args() args: UpdateProductArgs
+ ): Promise {
+ try {
+ return await this.service.update({
+ ...args,
+ data: args.data,
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @graphql.Mutation(() => Product)
+ @nestAccessControl.UseRoles({
+ resource: "Product",
+ action: "delete",
+ possession: "any",
+ })
+ async deleteProduct(
+ @graphql.Args() args: DeleteProductArgs
+ ): Promise {
+ try {
+ return await this.service.delete(args);
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.ResolveField(() => [Order])
+ @nestAccessControl.UseRoles({
+ resource: "Order",
+ action: "read",
+ possession: "any",
+ })
+ async orders(
+ @graphql.Parent() parent: Product,
+ @graphql.Args() args: OrderFindManyArgs
+ ): Promise {
+ const results = await this.service.findOrders(parent.id, args);
+
+ if (!results) {
+ return [];
+ }
+
+ return results;
+ }
+}
diff --git a/server/src/product/base/product.service.base.ts b/server/src/product/base/product.service.base.ts
new file mode 100644
index 0000000..9732b94
--- /dev/null
+++ b/server/src/product/base/product.service.base.ts
@@ -0,0 +1,60 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { PrismaService } from "nestjs-prisma";
+import { Prisma, Product, Order } from "@prisma/client";
+
+export class ProductServiceBase {
+ constructor(protected readonly prisma: PrismaService) {}
+
+ async count(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.product.count(args);
+ }
+
+ async findMany(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.product.findMany(args);
+ }
+ async findOne(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.product.findUnique(args);
+ }
+ async create(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.product.create(args);
+ }
+ async update(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.product.update(args);
+ }
+ async delete(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.product.delete(args);
+ }
+
+ async findOrders(
+ parentId: string,
+ args: Prisma.OrderFindManyArgs
+ ): Promise {
+ return this.prisma.product
+ .findUnique({
+ where: { id: parentId },
+ })
+ .orders(args);
+ }
+}
diff --git a/server/src/product/product.controller.ts b/server/src/product/product.controller.ts
new file mode 100644
index 0000000..b7c034f
--- /dev/null
+++ b/server/src/product/product.controller.ts
@@ -0,0 +1,17 @@
+import * as common from "@nestjs/common";
+import * as swagger from "@nestjs/swagger";
+import * as nestAccessControl from "nest-access-control";
+import { ProductService } from "./product.service";
+import { ProductControllerBase } from "./base/product.controller.base";
+
+@swagger.ApiTags("products")
+@common.Controller("products")
+export class ProductController extends ProductControllerBase {
+ constructor(
+ protected readonly service: ProductService,
+ @nestAccessControl.InjectRolesBuilder()
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {
+ super(service, rolesBuilder);
+ }
+}
diff --git a/server/src/product/product.module.ts b/server/src/product/product.module.ts
new file mode 100644
index 0000000..2fa2eec
--- /dev/null
+++ b/server/src/product/product.module.ts
@@ -0,0 +1,13 @@
+import { Module } from "@nestjs/common";
+import { ProductModuleBase } from "./base/product.module.base";
+import { ProductService } from "./product.service";
+import { ProductController } from "./product.controller";
+import { ProductResolver } from "./product.resolver";
+
+@Module({
+ imports: [ProductModuleBase],
+ controllers: [ProductController],
+ providers: [ProductService, ProductResolver],
+ exports: [ProductService],
+})
+export class ProductModule {}
diff --git a/server/src/product/product.resolver.ts b/server/src/product/product.resolver.ts
new file mode 100644
index 0000000..dc43a33
--- /dev/null
+++ b/server/src/product/product.resolver.ts
@@ -0,0 +1,20 @@
+import * as common from "@nestjs/common";
+import * as graphql from "@nestjs/graphql";
+import * as nestAccessControl from "nest-access-control";
+import { GqlDefaultAuthGuard } from "../auth/gqlDefaultAuth.guard";
+import * as gqlACGuard from "../auth/gqlAC.guard";
+import { ProductResolverBase } from "./base/product.resolver.base";
+import { Product } from "./base/Product";
+import { ProductService } from "./product.service";
+
+@graphql.Resolver(() => Product)
+@common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+export class ProductResolver extends ProductResolverBase {
+ constructor(
+ protected readonly service: ProductService,
+ @nestAccessControl.InjectRolesBuilder()
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {
+ super(service, rolesBuilder);
+ }
+}
diff --git a/server/src/product/product.service.ts b/server/src/product/product.service.ts
new file mode 100644
index 0000000..20d07b3
--- /dev/null
+++ b/server/src/product/product.service.ts
@@ -0,0 +1,10 @@
+import { Injectable } from "@nestjs/common";
+import { PrismaService } from "nestjs-prisma";
+import { ProductServiceBase } from "./base/product.service.base";
+
+@Injectable()
+export class ProductService extends ProductServiceBase {
+ constructor(protected readonly prisma: PrismaService) {
+ super(prisma);
+ }
+}
diff --git a/server/src/providers/secrets/base/secretsManager.service.base.ts b/server/src/providers/secrets/base/secretsManager.service.base.ts
new file mode 100644
index 0000000..18a340b
--- /dev/null
+++ b/server/src/providers/secrets/base/secretsManager.service.base.ts
@@ -0,0 +1,19 @@
+import { ConfigService } from "@nestjs/config";
+
+export interface ISecretsManager {
+ getSecret: (key: string) => Promise;
+}
+
+export class SecretsManagerServiceBase implements ISecretsManager {
+ constructor(protected readonly configService: ConfigService) {}
+ async getSecret(key: string): Promise {
+ if (!key) {
+ throw new Error("Didn't got the key");
+ }
+ const value = this.configService.get(key);
+ if (value) {
+ return value;
+ }
+ return null;
+ }
+}
diff --git a/server/src/providers/secrets/secretsManager.module.ts b/server/src/providers/secrets/secretsManager.module.ts
new file mode 100644
index 0000000..3a621e4
--- /dev/null
+++ b/server/src/providers/secrets/secretsManager.module.ts
@@ -0,0 +1,8 @@
+import { Module } from "@nestjs/common";
+import { SecretsManagerService } from "./secretsManager.service";
+
+@Module({
+ providers: [SecretsManagerService],
+ exports: [SecretsManagerService],
+})
+export class SecretsManagerModule {}
diff --git a/server/src/providers/secrets/secretsManager.service.ts b/server/src/providers/secrets/secretsManager.service.ts
new file mode 100644
index 0000000..89907c3
--- /dev/null
+++ b/server/src/providers/secrets/secretsManager.service.ts
@@ -0,0 +1,10 @@
+import { Injectable } from "@nestjs/common";
+import { ConfigService } from "@nestjs/config";
+import { SecretsManagerServiceBase } from "./base/secretsManager.service.base";
+
+@Injectable()
+export class SecretsManagerService extends SecretsManagerServiceBase {
+ constructor(protected readonly configService: ConfigService) {
+ super(configService);
+ }
+}
diff --git a/server/src/serveStaticOptions.service.ts b/server/src/serveStaticOptions.service.ts
new file mode 100644
index 0000000..d672b76
--- /dev/null
+++ b/server/src/serveStaticOptions.service.ts
@@ -0,0 +1,38 @@
+import * as path from "path";
+import { Injectable, Logger } from "@nestjs/common";
+import { ConfigService } from "@nestjs/config";
+import {
+ ServeStaticModuleOptions,
+ ServeStaticModuleOptionsFactory,
+} from "@nestjs/serve-static";
+
+const SERVE_STATIC_ROOT_PATH_VAR = "SERVE_STATIC_ROOT_PATH";
+const DEFAULT_STATIC_MODULE_OPTIONS_LIST: ServeStaticModuleOptions[] = [
+ {
+ serveRoot: "/swagger",
+ rootPath: path.join(__dirname, "swagger"),
+ },
+];
+
+@Injectable()
+export class ServeStaticOptionsService
+ implements ServeStaticModuleOptionsFactory {
+ private readonly logger = new Logger(ServeStaticOptionsService.name);
+
+ constructor(private readonly configService: ConfigService) {}
+
+ createLoggerOptions(): ServeStaticModuleOptions[] {
+ const serveStaticRootPath = this.configService.get(
+ SERVE_STATIC_ROOT_PATH_VAR
+ );
+ if (serveStaticRootPath) {
+ const resolvedPath = path.resolve(serveStaticRootPath);
+ this.logger.log(`Serving static files from ${resolvedPath}`);
+ return [
+ ...DEFAULT_STATIC_MODULE_OPTIONS_LIST,
+ { rootPath: resolvedPath, exclude: ["/api*", "/graphql"] },
+ ];
+ }
+ return DEFAULT_STATIC_MODULE_OPTIONS_LIST;
+ }
+}
diff --git a/server/src/swagger.ts b/server/src/swagger.ts
new file mode 100644
index 0000000..f921fe2
--- /dev/null
+++ b/server/src/swagger.ts
@@ -0,0 +1,21 @@
+import { DocumentBuilder } from "@nestjs/swagger";
+
+export const swaggerPath = "api";
+
+export const swaggerDocumentOptions = new DocumentBuilder()
+ .setTitle("Sample service")
+ .setDescription(
+ 'Sample service for e-commerce\n\n## Congratulations! Your service resource is ready.\n \nPlease note that all endpoints are secured with JWT Bearer authentication.\nBy default, your service resource comes with one user with the username "admin" and password "admin".\nLearn more in [our docs](https://docs.amplication.com)'
+ )
+ .setVersion("2lrvwyum")
+ .addBearerAuth()
+ .build();
+
+export const swaggerSetupOptions = {
+ swaggerOptions: {
+ persistAuthorization: true,
+ },
+ customCssUrl: "../swagger/swagger.css",
+ customfavIcon: "../swagger/favicon.png",
+ customSiteTitle: "Sample service",
+};
diff --git a/server/src/swagger/favicon.png b/server/src/swagger/favicon.png
new file mode 100644
index 0000000..a79882d
Binary files /dev/null and b/server/src/swagger/favicon.png differ
diff --git a/server/src/swagger/logo-amplication-white.svg b/server/src/swagger/logo-amplication-white.svg
new file mode 100644
index 0000000..0054cd4
--- /dev/null
+++ b/server/src/swagger/logo-amplication-white.svg
@@ -0,0 +1,15 @@
+
diff --git a/server/src/swagger/swagger.css b/server/src/swagger/swagger.css
new file mode 100644
index 0000000..3ff8e74
--- /dev/null
+++ b/server/src/swagger/swagger.css
@@ -0,0 +1,320 @@
+html,
+body {
+ background-color: #f4f4f7;
+}
+
+body {
+ margin: auto;
+ line-height: 1.6;
+ font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif;
+ color: #121242;
+}
+
+.swagger-ui {
+ font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif;
+}
+
+.swagger-ui button,
+.swagger-ui input,
+.swagger-ui optgroup,
+.swagger-ui select,
+.swagger-ui textarea,
+.swagger-ui .parameter__name,
+.swagger-ui .parameters-col_name > *,
+.swagger-ui label {
+ font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif;
+ font-weight: normal;
+ font-size: 12px;
+ outline: none;
+}
+
+.swagger-ui textarea {
+ border: 1px solid #d0d0d9;
+ min-height: 100px;
+}
+
+.swagger-ui input[type="email"],
+.swagger-ui input[type="file"],
+.swagger-ui input[type="password"],
+.swagger-ui input[type="search"],
+.swagger-ui input[type="text"],
+.swagger-ui textarea {
+ border-radius: 3px;
+}
+
+.swagger-ui input[disabled],
+.swagger-ui select[disabled],
+.swagger-ui textarea[disabled] {
+ background: #f4f4f7;
+ color: #b8b8c6;
+}
+
+.swagger-ui .btn {
+ font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif;
+ font-weight: 500;
+ box-shadow: none;
+ border: 1px solid #d0d0d9;
+ height: 28px;
+ border-radius: 14px;
+ background-color: #fff;
+ color: #7950ed;
+}
+
+.swagger-ui .btn:hover {
+ box-shadow: none;
+}
+
+/* topbar */
+
+.swagger-ui .topbar {
+ background-color: #7950ed;
+ height: 80px;
+ padding: 0;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+.swagger-ui .topbar-wrapper a {
+ display: block;
+ width: 206px;
+ height: 35px;
+ background-image: url("logo-amplication-white.svg");
+ background-repeat: no-repeat;
+ background-size: contain;
+}
+
+.swagger-ui .topbar-wrapper img {
+ display: none;
+}
+
+/* title */
+.swagger-ui .info {
+ margin: 0;
+}
+
+.swagger-ui .info .title {
+ font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif;
+ font-size: 32px;
+ font-weight: 600;
+}
+
+.swagger-ui .information-container {
+ padding-top: 50px;
+ padding-bottom: 20px;
+ position: relative;
+}
+
+.swagger-ui .info .title small.version-stamp {
+ display: none;
+}
+
+.swagger-ui .info .title small {
+ background-color: #a787ff;
+}
+
+.swagger-ui .info .description p {
+ max-width: 1000px;
+ margin: 0;
+}
+
+.swagger-ui .info .description p,
+.swagger-ui .info .description a {
+ font-size: 1rem;
+}
+
+.swagger-ui .information-container section {
+ position: relative;
+}
+
+.swagger-ui .scheme-container {
+ box-shadow: none;
+ background-color: transparent;
+ position: relative;
+ margin: 0;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ padding: 0;
+}
+
+.swagger-ui .scheme-container .auth-wrapper {
+ justify-content: flex-start;
+}
+
+.swagger-ui .btn.authorize {
+ box-shadow: none;
+ border: 1px solid #d0d0d9;
+ height: 40px;
+ border-radius: 20px;
+ background-color: #fff;
+ color: #7950ed;
+}
+
+.swagger-ui .btn.authorize svg {
+ fill: #7950ed;
+}
+
+/* content */
+
+.swagger-ui .opblock-tag {
+ font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+.swagger-ui .opblock.is-open .opblock-summary {
+ border-color: #e7e7ec !important;
+ border-bottom: none;
+}
+
+.swagger-ui .opblock .opblock-section-header {
+ background-color: #fff;
+ border: none;
+ border-top: 1px solid #e7e7ec;
+ border-bottom: 1px solid #e7e7ec;
+ box-shadow: none;
+}
+
+.swagger-ui .opblock .tab-header .tab-item.active h4 span:after {
+ display: none;
+}
+
+.swagger-ui .opblock.opblock-post {
+ border: 1px solid #e7e7ec;
+ background: #f9f9fa;
+ box-shadow: none;
+ color: #fff;
+}
+
+.swagger-ui .opblock.opblock-post:hover,
+.swagger-ui .opblock.opblock-post.is-open {
+ border-color: #31c587;
+}
+
+.swagger-ui .opblock.opblock-post .opblock-summary-method {
+ background-color: #31c587;
+}
+
+.swagger-ui .opblock.opblock-get {
+ border: 1px solid #e7e7ec;
+ background: #f9f9fa;
+ box-shadow: none;
+}
+.swagger-ui .opblock.opblock-get:hover,
+.swagger-ui .opblock.opblock-get.is-open {
+ border-color: #20a4f3;
+}
+.swagger-ui .opblock.opblock-get .opblock-summary-method {
+ background-color: #20a4f3;
+}
+
+.swagger-ui .opblock.opblock-delete {
+ border: 1px solid #e7e7ec;
+ background: #f9f9fa;
+ box-shadow: none;
+}
+.swagger-ui .opblock.opblock-delete:hover,
+.swagger-ui .opblock.opblock-delete.is-open {
+ border-color: #e93c51;
+}
+.swagger-ui .opblock.opblock-delete .opblock-summary-method {
+ background-color: #e93c51;
+}
+
+.swagger-ui .opblock.opblock-patch {
+ border: 1px solid #e7e7ec;
+ background: #f9f9fa;
+ box-shadow: none;
+}
+.swagger-ui .opblock.opblock-patch:hover,
+.swagger-ui .opblock.opblock-patch.is-open {
+ border-color: #41cadd;
+}
+.swagger-ui .opblock.opblock-patch .opblock-summary-method {
+ background-color: #41cadd;
+}
+
+.swagger-ui .opblock-body pre {
+ background-color: #121242 !important;
+}
+
+.swagger-ui select,
+.swagger-ui .response-control-media-type--accept-controller select {
+ border: 1px solid #d0d0d9;
+ box-shadow: none;
+ outline: none;
+}
+
+/* models */
+
+.swagger-ui section.models {
+ background-color: #fff;
+ border: 1px solid #e7e7ec;
+}
+
+.swagger-ui section.models.is-open h4 {
+ border-bottom: 1px solid #e7e7ec;
+}
+
+.swagger-ui section.models .model-container,
+.swagger-ui section.models .model-container:hover {
+ background-color: #f4f4f7;
+ color: #121242;
+}
+
+.swagger-ui .model-title {
+ font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif;
+ font-weight: normal;
+ font-size: 15px;
+ color: #121242;
+}
+
+/* modal */
+
+.swagger-ui .dialog-ux .modal-ux-header h3,
+.swagger-ui .dialog-ux .modal-ux-content h4,
+.swagger-ui .dialog-ux .modal-ux-content h4 code {
+ font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif;
+ font-weight: normal;
+ font-size: 15px;
+ color: #121242;
+}
+
+.swagger-ui .dialog-ux .modal-ux-content .btn.authorize {
+ height: 28px;
+ border-radius: 14px;
+}
+
+.swagger-ui .auth-btn-wrapper {
+ display: flex;
+ flex-direction: row-reverse;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+.swagger-ui .auth-btn-wrapper .btn-done {
+ border: none;
+ color: #121242;
+ margin-right: 0;
+}
+
+.swagger-ui .authorization__btn {
+ fill: #414168;
+}
diff --git a/server/src/tests/auth/constants.ts b/server/src/tests/auth/constants.ts
new file mode 100644
index 0000000..98e4887
--- /dev/null
+++ b/server/src/tests/auth/constants.ts
@@ -0,0 +1,19 @@
+import { Credentials } from "../../auth/Credentials";
+import { UserInfo } from "../../auth/UserInfo";
+
+export const VALID_ID = "1";
+
+export const TEST_USER: UserInfo = {
+ id: "cl7qmjh4h0000tothyjqapgj5",
+ roles: ["User"],
+ username: "ofek",
+};
+export const SIGN_TOKEN = "SIGN_TOKEN";
+export const VALID_CREDENTIALS: Credentials = {
+ username: "Valid User",
+ password: "Valid User Password",
+};
+export const INVALID_CREDENTIALS: Credentials = {
+ username: "Invalid User",
+ password: "Invalid User Password",
+};
diff --git a/server/src/tests/auth/token.service.spec.ts b/server/src/tests/auth/token.service.spec.ts
new file mode 100644
index 0000000..83b5a51
--- /dev/null
+++ b/server/src/tests/auth/token.service.spec.ts
@@ -0,0 +1,47 @@
+import { JwtService } from "@nestjs/jwt";
+import { mock } from "jest-mock-extended";
+import { TokenServiceBase } from "../../auth/base/token.service.base";
+import {
+ INVALID_PASSWORD_ERROR,
+ INVALID_USERNAME_ERROR,
+} from "../../auth/constants";
+import { SIGN_TOKEN, VALID_CREDENTIALS, VALID_ID } from "./constants";
+
+describe("Testing the TokenServiceBase", () => {
+ let tokenServiceBase: TokenServiceBase;
+ const jwtService = mock();
+ beforeEach(() => {
+ tokenServiceBase = new TokenServiceBase(jwtService);
+ jwtService.signAsync.mockClear();
+ });
+ describe("Testing the BasicTokenService.createToken()", () => {
+ it("should create valid token for valid username and password", async () => {
+ jwtService.signAsync.mockReturnValue(Promise.resolve(SIGN_TOKEN));
+ expect(
+ await tokenServiceBase.createToken({
+ id: VALID_ID,
+ username: VALID_CREDENTIALS.username,
+ password: VALID_CREDENTIALS.password,
+ })
+ ).toBe(SIGN_TOKEN);
+ });
+ it("should reject when username missing", () => {
+ const result = tokenServiceBase.createToken({
+ id: VALID_ID,
+ //@ts-ignore
+ username: null,
+ password: VALID_CREDENTIALS.password,
+ });
+ return expect(result).rejects.toBe(INVALID_USERNAME_ERROR);
+ });
+ it("should reject when password missing", () => {
+ const result = tokenServiceBase.createToken({
+ id: VALID_ID,
+ username: VALID_CREDENTIALS.username,
+ //@ts-ignore
+ password: null,
+ });
+ return expect(result).rejects.toBe(INVALID_PASSWORD_ERROR);
+ });
+ });
+});
diff --git a/server/src/types.ts b/server/src/types.ts
new file mode 100644
index 0000000..16a8bd9
--- /dev/null
+++ b/server/src/types.ts
@@ -0,0 +1,3 @@
+import { JsonValue } from "type-fest";
+
+export type InputJsonValue = Omit;
diff --git a/server/src/user/base/CreateUserArgs.ts b/server/src/user/base/CreateUserArgs.ts
new file mode 100644
index 0000000..07f8734
--- /dev/null
+++ b/server/src/user/base/CreateUserArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { UserCreateInput } from "./UserCreateInput";
+
+@ArgsType()
+class CreateUserArgs {
+ @Field(() => UserCreateInput, { nullable: false })
+ data!: UserCreateInput;
+}
+
+export { CreateUserArgs };
diff --git a/server/src/user/base/DeleteUserArgs.ts b/server/src/user/base/DeleteUserArgs.ts
new file mode 100644
index 0000000..83ba382
--- /dev/null
+++ b/server/src/user/base/DeleteUserArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { UserWhereUniqueInput } from "./UserWhereUniqueInput";
+
+@ArgsType()
+class DeleteUserArgs {
+ @Field(() => UserWhereUniqueInput, { nullable: false })
+ where!: UserWhereUniqueInput;
+}
+
+export { DeleteUserArgs };
diff --git a/server/src/user/base/UpdateUserArgs.ts b/server/src/user/base/UpdateUserArgs.ts
new file mode 100644
index 0000000..4b3d92d
--- /dev/null
+++ b/server/src/user/base/UpdateUserArgs.ts
@@ -0,0 +1,24 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { UserWhereUniqueInput } from "./UserWhereUniqueInput";
+import { UserUpdateInput } from "./UserUpdateInput";
+
+@ArgsType()
+class UpdateUserArgs {
+ @Field(() => UserWhereUniqueInput, { nullable: false })
+ where!: UserWhereUniqueInput;
+ @Field(() => UserUpdateInput, { nullable: false })
+ data!: UserUpdateInput;
+}
+
+export { UpdateUserArgs };
diff --git a/server/src/user/base/User.ts b/server/src/user/base/User.ts
new file mode 100644
index 0000000..38f9fa8
--- /dev/null
+++ b/server/src/user/base/User.ts
@@ -0,0 +1,81 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ObjectType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsDate, IsString, IsOptional, IsJSON } from "class-validator";
+import { Type } from "class-transformer";
+import { GraphQLJSON } from "graphql-type-json";
+import { JsonValue } from "type-fest";
+@ObjectType()
+class User {
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ createdAt!: Date;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ firstName!: string | null;
+
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ lastName!: string | null;
+
+ @ApiProperty({
+ required: true,
+ })
+ @IsJSON()
+ @Field(() => GraphQLJSON)
+ roles!: JsonValue;
+
+ @ApiProperty({
+ required: true,
+ })
+ @IsDate()
+ @Type(() => Date)
+ @Field(() => Date)
+ updatedAt!: Date;
+
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ username!: string;
+}
+export { User };
diff --git a/server/src/user/base/UserCreateInput.ts b/server/src/user/base/UserCreateInput.ts
new file mode 100644
index 0000000..f228703
--- /dev/null
+++ b/server/src/user/base/UserCreateInput.ts
@@ -0,0 +1,64 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsString, IsOptional, IsJSON } from "class-validator";
+import { GraphQLJSON } from "graphql-type-json";
+import { InputJsonValue } from "../../types";
+@InputType()
+class UserCreateInput {
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ firstName?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ lastName?: string | null;
+
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ password!: string;
+
+ @ApiProperty({
+ required: true,
+ })
+ @IsJSON()
+ @Field(() => GraphQLJSON)
+ roles!: InputJsonValue;
+
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ username!: string;
+}
+export { UserCreateInput };
diff --git a/server/src/user/base/UserFindManyArgs.ts b/server/src/user/base/UserFindManyArgs.ts
new file mode 100644
index 0000000..3b0743f
--- /dev/null
+++ b/server/src/user/base/UserFindManyArgs.ts
@@ -0,0 +1,53 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { UserWhereInput } from "./UserWhereInput";
+import { Type } from "class-transformer";
+import { UserOrderByInput } from "./UserOrderByInput";
+
+@ArgsType()
+class UserFindManyArgs {
+ @ApiProperty({
+ required: false,
+ type: () => UserWhereInput,
+ })
+ @Field(() => UserWhereInput, { nullable: true })
+ @Type(() => UserWhereInput)
+ where?: UserWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: [UserOrderByInput],
+ })
+ @Field(() => [UserOrderByInput], { nullable: true })
+ @Type(() => UserOrderByInput)
+ orderBy?: Array;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ skip?: number;
+
+ @ApiProperty({
+ required: false,
+ type: Number,
+ })
+ @Field(() => Number, { nullable: true })
+ @Type(() => Number)
+ take?: number;
+}
+
+export { UserFindManyArgs };
diff --git a/server/src/user/base/UserFindUniqueArgs.ts b/server/src/user/base/UserFindUniqueArgs.ts
new file mode 100644
index 0000000..67ccc2c
--- /dev/null
+++ b/server/src/user/base/UserFindUniqueArgs.ts
@@ -0,0 +1,21 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { ArgsType, Field } from "@nestjs/graphql";
+import { UserWhereUniqueInput } from "./UserWhereUniqueInput";
+
+@ArgsType()
+class UserFindUniqueArgs {
+ @Field(() => UserWhereUniqueInput, { nullable: false })
+ where!: UserWhereUniqueInput;
+}
+
+export { UserFindUniqueArgs };
diff --git a/server/src/user/base/UserListRelationFilter.ts b/server/src/user/base/UserListRelationFilter.ts
new file mode 100644
index 0000000..c0b666d
--- /dev/null
+++ b/server/src/user/base/UserListRelationFilter.ts
@@ -0,0 +1,56 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { UserWhereInput } from "./UserWhereInput";
+import { ValidateNested, IsOptional } from "class-validator";
+import { Type } from "class-transformer";
+
+@InputType()
+class UserListRelationFilter {
+ @ApiProperty({
+ required: false,
+ type: () => UserWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => UserWhereInput)
+ @IsOptional()
+ @Field(() => UserWhereInput, {
+ nullable: true,
+ })
+ every?: UserWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => UserWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => UserWhereInput)
+ @IsOptional()
+ @Field(() => UserWhereInput, {
+ nullable: true,
+ })
+ some?: UserWhereInput;
+
+ @ApiProperty({
+ required: false,
+ type: () => UserWhereInput,
+ })
+ @ValidateNested()
+ @Type(() => UserWhereInput)
+ @IsOptional()
+ @Field(() => UserWhereInput, {
+ nullable: true,
+ })
+ none?: UserWhereInput;
+}
+export { UserListRelationFilter };
diff --git a/server/src/user/base/UserOrderByInput.ts b/server/src/user/base/UserOrderByInput.ts
new file mode 100644
index 0000000..5aefc17
--- /dev/null
+++ b/server/src/user/base/UserOrderByInput.ts
@@ -0,0 +1,94 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { SortOrder } from "../../util/SortOrder";
+
+@InputType({
+ isAbstract: true,
+ description: undefined,
+})
+class UserOrderByInput {
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ createdAt?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ firstName?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ id?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ lastName?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ password?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ roles?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ updatedAt?: SortOrder;
+
+ @ApiProperty({
+ required: false,
+ enum: ["asc", "desc"],
+ })
+ @Field(() => SortOrder, {
+ nullable: true,
+ })
+ username?: SortOrder;
+}
+
+export { UserOrderByInput };
diff --git a/server/src/user/base/UserUpdateInput.ts b/server/src/user/base/UserUpdateInput.ts
new file mode 100644
index 0000000..b273d74
--- /dev/null
+++ b/server/src/user/base/UserUpdateInput.ts
@@ -0,0 +1,73 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsString, IsOptional, IsJSON } from "class-validator";
+import { GraphQLJSON } from "graphql-type-json";
+import { InputJsonValue } from "../../types";
+@InputType()
+class UserUpdateInput {
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ firstName?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ lastName?: string | null;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ password?: string;
+
+ @ApiProperty({
+ required: false,
+ })
+ @IsJSON()
+ @IsOptional()
+ @Field(() => GraphQLJSON, {
+ nullable: true,
+ })
+ roles?: InputJsonValue;
+
+ @ApiProperty({
+ required: false,
+ type: String,
+ })
+ @IsString()
+ @IsOptional()
+ @Field(() => String, {
+ nullable: true,
+ })
+ username?: string;
+}
+export { UserUpdateInput };
diff --git a/server/src/user/base/UserWhereInput.ts b/server/src/user/base/UserWhereInput.ts
new file mode 100644
index 0000000..d75725a
--- /dev/null
+++ b/server/src/user/base/UserWhereInput.ts
@@ -0,0 +1,64 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { StringNullableFilter } from "../../util/StringNullableFilter";
+import { Type } from "class-transformer";
+import { IsOptional } from "class-validator";
+import { StringFilter } from "../../util/StringFilter";
+@InputType()
+class UserWhereInput {
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ firstName?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringFilter,
+ })
+ @Type(() => StringFilter)
+ @IsOptional()
+ @Field(() => StringFilter, {
+ nullable: true,
+ })
+ id?: StringFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringNullableFilter,
+ })
+ @Type(() => StringNullableFilter)
+ @IsOptional()
+ @Field(() => StringNullableFilter, {
+ nullable: true,
+ })
+ lastName?: StringNullableFilter;
+
+ @ApiProperty({
+ required: false,
+ type: StringFilter,
+ })
+ @Type(() => StringFilter)
+ @IsOptional()
+ @Field(() => StringFilter, {
+ nullable: true,
+ })
+ username?: StringFilter;
+}
+export { UserWhereInput };
diff --git a/server/src/user/base/UserWhereUniqueInput.ts b/server/src/user/base/UserWhereUniqueInput.ts
new file mode 100644
index 0000000..10c1590
--- /dev/null
+++ b/server/src/user/base/UserWhereUniqueInput.ts
@@ -0,0 +1,25 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { InputType, Field } from "@nestjs/graphql";
+import { ApiProperty } from "@nestjs/swagger";
+import { IsString } from "class-validator";
+@InputType()
+class UserWhereUniqueInput {
+ @ApiProperty({
+ required: true,
+ type: String,
+ })
+ @IsString()
+ @Field(() => String)
+ id!: string;
+}
+export { UserWhereUniqueInput };
diff --git a/server/src/user/base/user.controller.base.spec.ts b/server/src/user/base/user.controller.base.spec.ts
new file mode 100644
index 0000000..b9b88de
--- /dev/null
+++ b/server/src/user/base/user.controller.base.spec.ts
@@ -0,0 +1,185 @@
+import { Test } from "@nestjs/testing";
+import {
+ INestApplication,
+ HttpStatus,
+ ExecutionContext,
+ CallHandler,
+} from "@nestjs/common";
+import request from "supertest";
+import { MorganModule } from "nest-morgan";
+import { ACGuard } from "nest-access-control";
+import { DefaultAuthGuard } from "../../auth/defaultAuth.guard";
+import { ACLModule } from "../../auth/acl.module";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { map } from "rxjs";
+import { UserController } from "../user.controller";
+import { UserService } from "../user.service";
+
+const nonExistingId = "nonExistingId";
+const existingId = "existingId";
+const CREATE_INPUT = {
+ createdAt: new Date(),
+ firstName: "exampleFirstName",
+ id: "exampleId",
+ lastName: "exampleLastName",
+ password: "examplePassword",
+ updatedAt: new Date(),
+ username: "exampleUsername",
+};
+const CREATE_RESULT = {
+ createdAt: new Date(),
+ firstName: "exampleFirstName",
+ id: "exampleId",
+ lastName: "exampleLastName",
+ password: "examplePassword",
+ updatedAt: new Date(),
+ username: "exampleUsername",
+};
+const FIND_MANY_RESULT = [
+ {
+ createdAt: new Date(),
+ firstName: "exampleFirstName",
+ id: "exampleId",
+ lastName: "exampleLastName",
+ password: "examplePassword",
+ updatedAt: new Date(),
+ username: "exampleUsername",
+ },
+];
+const FIND_ONE_RESULT = {
+ createdAt: new Date(),
+ firstName: "exampleFirstName",
+ id: "exampleId",
+ lastName: "exampleLastName",
+ password: "examplePassword",
+ updatedAt: new Date(),
+ username: "exampleUsername",
+};
+
+const service = {
+ create() {
+ return CREATE_RESULT;
+ },
+ findMany: () => FIND_MANY_RESULT,
+ findOne: ({ where }: { where: { id: string } }) => {
+ switch (where.id) {
+ case existingId:
+ return FIND_ONE_RESULT;
+ case nonExistingId:
+ return null;
+ }
+ },
+};
+
+const basicAuthGuard = {
+ canActivate: (context: ExecutionContext) => {
+ const argumentHost = context.switchToHttp();
+ const request = argumentHost.getRequest();
+ request.user = {
+ roles: ["user"],
+ };
+ return true;
+ },
+};
+
+const acGuard = {
+ canActivate: () => {
+ return true;
+ },
+};
+
+const aclFilterResponseInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle().pipe(
+ map((data) => {
+ return data;
+ })
+ );
+ },
+};
+const aclValidateRequestInterceptor = {
+ intercept: (context: ExecutionContext, next: CallHandler) => {
+ return next.handle();
+ },
+};
+
+describe("User", () => {
+ let app: INestApplication;
+
+ beforeAll(async () => {
+ const moduleRef = await Test.createTestingModule({
+ providers: [
+ {
+ provide: UserService,
+ useValue: service,
+ },
+ ],
+ controllers: [UserController],
+ imports: [MorganModule.forRoot(), ACLModule],
+ })
+ .overrideGuard(DefaultAuthGuard)
+ .useValue(basicAuthGuard)
+ .overrideGuard(ACGuard)
+ .useValue(acGuard)
+ .overrideInterceptor(AclFilterResponseInterceptor)
+ .useValue(aclFilterResponseInterceptor)
+ .overrideInterceptor(AclValidateRequestInterceptor)
+ .useValue(aclValidateRequestInterceptor)
+ .compile();
+
+ app = moduleRef.createNestApplication();
+ await app.init();
+ });
+
+ test("POST /users", async () => {
+ await request(app.getHttpServer())
+ .post("/users")
+ .send(CREATE_INPUT)
+ .expect(HttpStatus.CREATED)
+ .expect({
+ ...CREATE_RESULT,
+ createdAt: CREATE_RESULT.createdAt.toISOString(),
+ updatedAt: CREATE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ test("GET /users", async () => {
+ await request(app.getHttpServer())
+ .get("/users")
+ .expect(HttpStatus.OK)
+ .expect([
+ {
+ ...FIND_MANY_RESULT[0],
+ createdAt: FIND_MANY_RESULT[0].createdAt.toISOString(),
+ updatedAt: FIND_MANY_RESULT[0].updatedAt.toISOString(),
+ },
+ ]);
+ });
+
+ test("GET /users/:id non existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/users"}/${nonExistingId}`)
+ .expect(HttpStatus.NOT_FOUND)
+ .expect({
+ statusCode: HttpStatus.NOT_FOUND,
+ message: `No resource was found for {"${"id"}":"${nonExistingId}"}`,
+ error: "Not Found",
+ });
+ });
+
+ test("GET /users/:id existing", async () => {
+ await request(app.getHttpServer())
+ .get(`${"/users"}/${existingId}`)
+ .expect(HttpStatus.OK)
+ .expect({
+ ...FIND_ONE_RESULT,
+ createdAt: FIND_ONE_RESULT.createdAt.toISOString(),
+ updatedAt: FIND_ONE_RESULT.updatedAt.toISOString(),
+ });
+ });
+
+ afterAll(async () => {
+ await app.close();
+ });
+});
diff --git a/server/src/user/base/user.controller.base.ts b/server/src/user/base/user.controller.base.ts
new file mode 100644
index 0000000..e1c9efd
--- /dev/null
+++ b/server/src/user/base/user.controller.base.ts
@@ -0,0 +1,193 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as swagger from "@nestjs/swagger";
+import * as nestAccessControl from "nest-access-control";
+import * as defaultAuthGuard from "../../auth/defaultAuth.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import * as errors from "../../errors";
+import { Request } from "express";
+import { plainToClass } from "class-transformer";
+import { ApiNestedQuery } from "../../decorators/api-nested-query.decorator";
+import { UserService } from "../user.service";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { UserCreateInput } from "./UserCreateInput";
+import { UserWhereInput } from "./UserWhereInput";
+import { UserWhereUniqueInput } from "./UserWhereUniqueInput";
+import { UserFindManyArgs } from "./UserFindManyArgs";
+import { UserUpdateInput } from "./UserUpdateInput";
+import { User } from "./User";
+@swagger.ApiBearerAuth()
+@common.UseGuards(defaultAuthGuard.DefaultAuthGuard, nestAccessControl.ACGuard)
+export class UserControllerBase {
+ constructor(
+ protected readonly service: UserService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "create",
+ possession: "any",
+ })
+ @common.Post()
+ @swagger.ApiCreatedResponse({ type: User })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async create(@common.Body() data: UserCreateInput): Promise {
+ return await this.service.create({
+ data: data,
+ select: {
+ createdAt: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ roles: true,
+ updatedAt: true,
+ username: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "read",
+ possession: "any",
+ })
+ @common.Get()
+ @swagger.ApiOkResponse({ type: [User] })
+ @swagger.ApiForbiddenResponse()
+ @ApiNestedQuery(UserFindManyArgs)
+ async findMany(@common.Req() request: Request): Promise {
+ const args = plainToClass(UserFindManyArgs, request.query);
+ return this.service.findMany({
+ ...args,
+ select: {
+ createdAt: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ roles: true,
+ updatedAt: true,
+ username: true,
+ },
+ });
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "read",
+ possession: "own",
+ })
+ @common.Get("/:id")
+ @swagger.ApiOkResponse({ type: User })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async findOne(
+ @common.Param() params: UserWhereUniqueInput
+ ): Promise {
+ const result = await this.service.findOne({
+ where: params,
+ select: {
+ createdAt: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ roles: true,
+ updatedAt: true,
+ username: true,
+ },
+ });
+ if (result === null) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "update",
+ possession: "any",
+ })
+ @common.Patch("/:id")
+ @swagger.ApiOkResponse({ type: User })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async update(
+ @common.Param() params: UserWhereUniqueInput,
+ @common.Body() data: UserUpdateInput
+ ): Promise {
+ try {
+ return await this.service.update({
+ where: params,
+ data: data,
+ select: {
+ createdAt: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ roles: true,
+ updatedAt: true,
+ username: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "delete",
+ possession: "any",
+ })
+ @common.Delete("/:id")
+ @swagger.ApiOkResponse({ type: User })
+ @swagger.ApiNotFoundResponse({ type: errors.NotFoundException })
+ @swagger.ApiForbiddenResponse({ type: errors.ForbiddenException })
+ async delete(
+ @common.Param() params: UserWhereUniqueInput
+ ): Promise {
+ try {
+ return await this.service.delete({
+ where: params,
+ select: {
+ createdAt: true,
+ firstName: true,
+ id: true,
+ lastName: true,
+ roles: true,
+ updatedAt: true,
+ username: true,
+ },
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new errors.NotFoundException(
+ `No resource was found for ${JSON.stringify(params)}`
+ );
+ }
+ throw error;
+ }
+ }
+}
diff --git a/server/src/user/base/user.module.base.ts b/server/src/user/base/user.module.base.ts
new file mode 100644
index 0000000..66fbdc2
--- /dev/null
+++ b/server/src/user/base/user.module.base.ts
@@ -0,0 +1,28 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { Module, forwardRef } from "@nestjs/common";
+import { MorganModule } from "nest-morgan";
+import { PrismaModule } from "nestjs-prisma";
+import { ACLModule } from "../../auth/acl.module";
+import { AuthModule } from "../../auth/auth.module";
+
+@Module({
+ imports: [
+ ACLModule,
+ forwardRef(() => AuthModule),
+ MorganModule,
+ PrismaModule,
+ ],
+
+ exports: [ACLModule, AuthModule, MorganModule, PrismaModule],
+})
+export class UserModuleBase {}
diff --git a/server/src/user/base/user.resolver.base.ts b/server/src/user/base/user.resolver.base.ts
new file mode 100644
index 0000000..26b1a66
--- /dev/null
+++ b/server/src/user/base/user.resolver.base.ts
@@ -0,0 +1,138 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import * as common from "@nestjs/common";
+import * as graphql from "@nestjs/graphql";
+import * as apollo from "apollo-server-express";
+import * as nestAccessControl from "nest-access-control";
+import { GqlDefaultAuthGuard } from "../../auth/gqlDefaultAuth.guard";
+import * as gqlACGuard from "../../auth/gqlAC.guard";
+import { isRecordNotFoundError } from "../../prisma.util";
+import { MetaQueryPayload } from "../../util/MetaQueryPayload";
+import { AclFilterResponseInterceptor } from "../../interceptors/aclFilterResponse.interceptor";
+import { AclValidateRequestInterceptor } from "../../interceptors/aclValidateRequest.interceptor";
+import { CreateUserArgs } from "./CreateUserArgs";
+import { UpdateUserArgs } from "./UpdateUserArgs";
+import { DeleteUserArgs } from "./DeleteUserArgs";
+import { UserFindManyArgs } from "./UserFindManyArgs";
+import { UserFindUniqueArgs } from "./UserFindUniqueArgs";
+import { User } from "./User";
+import { UserService } from "../user.service";
+
+@graphql.Resolver(() => User)
+@common.UseGuards(GqlDefaultAuthGuard, gqlACGuard.GqlACGuard)
+export class UserResolverBase {
+ constructor(
+ protected readonly service: UserService,
+ protected readonly rolesBuilder: nestAccessControl.RolesBuilder
+ ) {}
+
+ @graphql.Query(() => MetaQueryPayload)
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "read",
+ possession: "any",
+ })
+ async _usersMeta(
+ @graphql.Args() args: UserFindManyArgs
+ ): Promise {
+ const results = await this.service.count({
+ ...args,
+ skip: undefined,
+ take: undefined,
+ });
+ return {
+ count: results,
+ };
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => [User])
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "read",
+ possession: "any",
+ })
+ async users(@graphql.Args() args: UserFindManyArgs): Promise {
+ return this.service.findMany(args);
+ }
+
+ @common.UseInterceptors(AclFilterResponseInterceptor)
+ @graphql.Query(() => User, { nullable: true })
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "read",
+ possession: "own",
+ })
+ async user(@graphql.Args() args: UserFindUniqueArgs): Promise {
+ const result = await this.service.findOne(args);
+ if (result === null) {
+ return null;
+ }
+ return result;
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => User)
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "create",
+ possession: "any",
+ })
+ async createUser(@graphql.Args() args: CreateUserArgs): Promise {
+ return await this.service.create({
+ ...args,
+ data: args.data,
+ });
+ }
+
+ @common.UseInterceptors(AclValidateRequestInterceptor)
+ @graphql.Mutation(() => User)
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "update",
+ possession: "any",
+ })
+ async updateUser(@graphql.Args() args: UpdateUserArgs): Promise {
+ try {
+ return await this.service.update({
+ ...args,
+ data: args.data,
+ });
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+
+ @graphql.Mutation(() => User)
+ @nestAccessControl.UseRoles({
+ resource: "User",
+ action: "delete",
+ possession: "any",
+ })
+ async deleteUser(@graphql.Args() args: DeleteUserArgs): Promise {
+ try {
+ return await this.service.delete(args);
+ } catch (error) {
+ if (isRecordNotFoundError(error)) {
+ throw new apollo.ApolloError(
+ `No resource was found for ${JSON.stringify(args.where)}`
+ );
+ }
+ throw error;
+ }
+ }
+}
diff --git a/server/src/user/base/user.service.base.ts b/server/src/user/base/user.service.base.ts
new file mode 100644
index 0000000..ce23a64
--- /dev/null
+++ b/server/src/user/base/user.service.base.ts
@@ -0,0 +1,74 @@
+/*
+------------------------------------------------------------------------------
+This code was generated by Amplication.
+
+Changes to this file will be lost if the code is regenerated.
+
+There are other ways to to customize your code, see this doc to learn more
+https://docs.amplication.com/docs/how-to/custom-code
+
+------------------------------------------------------------------------------
+ */
+import { PrismaService } from "nestjs-prisma";
+import { Prisma, User } from "@prisma/client";
+import { PasswordService } from "../../auth/password.service";
+import { transformStringFieldUpdateInput } from "../../prisma.util";
+
+export class UserServiceBase {
+ constructor(
+ protected readonly prisma: PrismaService,
+ protected readonly passwordService: PasswordService
+ ) {}
+
+ async count(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.user.count(args);
+ }
+
+ async findMany(
+ args: Prisma.SelectSubset
+ ): Promise {
+ return this.prisma.user.findMany(args);
+ }
+ async findOne(
+ args: Prisma.SelectSubset
+ ): Promise