Initial Files

This commit is contained in:
Damien Ostler 2024-02-04 01:03:43 -05:00
commit 003bb0413c
298 changed files with 31890 additions and 0 deletions

11
GitVersion.yml Normal file
View File

@ -0,0 +1,11 @@
assembly-versioning-scheme: MajorMinorPatch
mode: ContinuousDelivery
branches: {}
ignore:
sha: []
merge-message-formats: {}
commit-message-incrementing: Enabled
major-version-bump-message: "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?(!:|:.*\\n\\n((.+\\n)+\\n)?BREAKING CHANGE:\\s.+)"
minor-version-bump-message: "^(feat)(\\([\\w\\s-]*\\))?:"
patch-version-bump-message: "^(build|chore|ci|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?:"

71
README.md Normal file
View File

@ -0,0 +1,71 @@
# Comissions.app
## Description
This is a application that provides a platform to allow creatives to sell their services without intense moderation.
## Table of Contents
- [Project Name](#project-name)
- [Description](#description)
- [Table of Contents](#table-of-contents)
- [Prerequisites](#prerequisites)
- [Getting Started](#getting-started)
- [Clone the Repository](#clone-the-repository)
- [Build and Run with Docker](#build-and-run-with-docker)
- [Usage](#usage)
- [Swagger UI](#swagger-ui)
- [Contributing](#contributing)
- [License](#license)
## Prerequisites
- [.NET 8 SDK](https://dotnet.microsoft.com/download)
- [Docker](https://www.docker.com/get-started)
## Getting Started
### Clone the Repository
```bash
git clone https://github.com/D4M13N-D3V/art_platform
cd art_platform
```
### Build and Run with Docker
Build the Docker image:
```bash
docker build -t art-platform -f ./src/ArtPlatform.API/Dockerfile --force-rm .
```
Run the Docker container:
```bash
docker run -p 8080:80 art-platform
```
The API should be accessible at `http://localhost:8080`.
## Usage
Describe how to use your API and any specific details or considerations that users need to be aware of.
## Swagger UI
The API is documented using Swagger UI. Once the application is running, you can access the Swagger UI at:
```plaintext
http://localhost:8080/swagger
```
This provides an interactive interface for testing and exploring your API endpoints.
## Contributing
If you would like to contribute to the project, please follow the guidelines in [CONTRIBUTING.md](CONTRIBUTING.md).
## License
This project is licensed under the [License Name] - see the [LICENSE.md](LICENSE.md) file for details.

View File

@ -0,0 +1,59 @@
<mxfile host="Electron" modified="2024-01-29T05:30:57.240Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.2 Chrome/114.0.5735.289 Electron/25.9.4 Safari/537.36" etag="hqyKwuYn-BmlLsgfQ51i" version="22.1.2" type="device">
<diagram name="Page-1" id="JpjQ_aS7q1FhBQwQmMJn">
<mxGraphModel dx="1434" dy="838" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="RHmSW2tqYuRQQK1HVGpt-1" value="comissions.app" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="70" y="210" width="270" height="220" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-2" value="API" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="RHmSW2tqYuRQQK1HVGpt-1">
<mxGeometry x="131" y="80" width="120" height="20" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-3" value=".NET 8 ASP.NET" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="RHmSW2tqYuRQQK1HVGpt-1">
<mxGeometry x="130" y="100" width="120" height="20" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-4" value="UI" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="RHmSW2tqYuRQQK1HVGpt-1">
<mxGeometry x="131" y="140" width="120" height="20" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-5" value="React NextJS" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="RHmSW2tqYuRQQK1HVGpt-1">
<mxGeometry x="130" y="160" width="120" height="20" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-6" value="NPGSQL" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="RHmSW2tqYuRQQK1HVGpt-1">
<mxGeometry x="13" y="58" width="60" height="80" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.25;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=27.5;entryPerimeter=0;startArrow=classic;startFill=1;" edge="1" parent="RHmSW2tqYuRQQK1HVGpt-1" source="RHmSW2tqYuRQQK1HVGpt-3" target="RHmSW2tqYuRQQK1HVGpt-6">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-8" value="Auth0" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="383" y="220" width="75" height="50" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-9" value="Novu" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="383" y="292" width="75" height="50" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-10" value="Stripe" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="383" y="360" width="75" height="50" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.16;entryY=0.55;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="RHmSW2tqYuRQQK1HVGpt-3" target="RHmSW2tqYuRQQK1HVGpt-8">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="360" y="320" />
<mxPoint x="360" y="248" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.16;entryY=0.55;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="RHmSW2tqYuRQQK1HVGpt-3" target="RHmSW2tqYuRQQK1HVGpt-9">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="RHmSW2tqYuRQQK1HVGpt-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.16;entryY=0.55;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="RHmSW2tqYuRQQK1HVGpt-3" target="RHmSW2tqYuRQQK1HVGpt-10">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="360" y="320" />
<mxPoint x="360" y="388" />
</Array>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

326
docs/database_design.drawio Normal file
View File

@ -0,0 +1,326 @@
<mxfile host="Electron" modified="2024-01-29T05:08:53.410Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.2 Chrome/114.0.5735.289 Electron/25.9.4 Safari/537.36" etag="D43PAxJE1Ifgl-ociygT" version="22.1.2" type="device">
<diagram name="Page-1" id="EOcqesTEYwX42nReKtH7">
<mxGraphModel dx="1434" dy="838" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="UY2qzOe5k0s0eoKJkEdN-1" value="&lt;h1&gt;Database Design&lt;/h1&gt;" style="text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;align=center;" parent="1" vertex="1">
<mxGeometry x="355" y="160" width="850" height="40" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-2" value="SellerProfilePortfolioPiece" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="850" y="880" width="190" height="150" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-3" value="PK Id:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-2" vertex="1">
<mxGeometry y="30" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-5" value="FK SellerServiceId:int&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-2" vertex="1">
<mxGeometry y="60" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-4" value="FK SellerProfileId:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-2" vertex="1">
<mxGeometry y="90" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-11" value="FileReference:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-2" vertex="1">
<mxGeometry y="120" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-12" value="SellerProfileRequest" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="300" y="80" width="140" height="180" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-13" value="PK Id:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-12" vertex="1">
<mxGeometry y="30" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-14" value="FK UserId:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-12" vertex="1">
<mxGeometry y="60" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-15" value="RequestDate:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-12" vertex="1">
<mxGeometry y="90" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-16" value="AcceptedDate:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-12" vertex="1">
<mxGeometry y="120" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-17" value="Accepted:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-12" vertex="1">
<mxGeometry y="150" width="140" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-25" value="SellerServiceOrder" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="820" y="380" width="210" height="300" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-26" value="PK Id:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-25" vertex="1">
<mxGeometry y="30" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-27" value="FK BuyerId:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-25" vertex="1">
<mxGeometry y="60" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-28" value="FK SellerServiceId:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-25" vertex="1">
<mxGeometry y="90" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-29" value="FK SellerId:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-25" vertex="1">
<mxGeometry y="120" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-30" value="Status:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-25" vertex="1">
<mxGeometry y="150" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-31" value="Price:double" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-25" vertex="1">
<mxGeometry y="180" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-32" value="CreatedDate:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-25" vertex="1">
<mxGeometry y="210" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-33" value="TermsAcceptedDate:date&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-25" vertex="1">
<mxGeometry y="240" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-34" value="EndDate:date&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-25" vertex="1">
<mxGeometry y="270" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-35" value="SellerServiceOrderMessage" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="1130" y="110" width="190" height="180" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-36" value="PK Id:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-35" vertex="1">
<mxGeometry y="30" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-38" value="FK SenderId:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-35" vertex="1">
<mxGeometry y="60" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-37" value="FK SellerServiceOrderId:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-35" vertex="1">
<mxGeometry y="90" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-39" value="Message:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-35" vertex="1">
<mxGeometry y="120" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-40" value="SentAt:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-35" vertex="1">
<mxGeometry y="150" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-41" value="SellerServiceOrderMessageAttachment" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="1380" y="80" width="221" height="120" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-42" value="PK Id:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-41" vertex="1">
<mxGeometry y="30" width="221" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-43" value="FK SellerServiceOrderMessageId:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-41" vertex="1">
<mxGeometry y="60" width="221" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-44" value="FileReference:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-41" vertex="1">
<mxGeometry y="90" width="221" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-55" value="SellerServiceOrderReview" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="1130" y="320" width="190" height="240" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-56" value="PK Id:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-55" vertex="1">
<mxGeometry y="30" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-59" value="FK Reviwer:string&amp;nbsp;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-55" vertex="1">
<mxGeometry y="60" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-57" value="FK SellerServiceOrderId:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-55" vertex="1">
<mxGeometry y="90" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-58" value="FK&amp;nbsp;SellerServiceId:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-55" vertex="1">
<mxGeometry y="120" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-34" value="ReviewDate:date" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="UY2qzOe5k0s0eoKJkEdN-55">
<mxGeometry y="150" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-60" value="Review:string&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-55" vertex="1">
<mxGeometry y="180" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-61" value="Rating:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-55" vertex="1">
<mxGeometry y="210" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-62" value="User" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="30" y="270" width="210" height="480" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-63" value="PK Id:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="30" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-64" value="FK UserSellerProfileId:int&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="60" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-65" value="DisplayName:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="90" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-67" value="Biography:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="120" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-68" value="Email:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="150" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-69" value="Banned:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="180" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-70" value="BannedDate:date&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="210" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-71" value="UnbanDate:date&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="240" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-72" value="BanReason:string&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="270" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-73" value="BanAdminId:string&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-62" vertex="1">
<mxGeometry y="300" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-2" value="Suspended:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="UY2qzOe5k0s0eoKJkEdN-62">
<mxGeometry y="330" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-3" value="SuspendedDate:date&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="UY2qzOe5k0s0eoKJkEdN-62">
<mxGeometry y="360" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-4" value="UnsuspendDate:date&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="UY2qzOe5k0s0eoKJkEdN-62">
<mxGeometry y="390" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-5" value="SuspendReason:string&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="UY2qzOe5k0s0eoKJkEdN-62">
<mxGeometry y="420" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-1" value="SuspendAdminId:string&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="UY2qzOe5k0s0eoKJkEdN-62">
<mxGeometry y="450" width="210" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-6" value="UserSellerProfile" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="355" y="690" width="175" height="390" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-7" value="PK Id:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="30" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-8" value="FK UserId:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="60" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-9" value="Biography:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="90" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-10" value="SocialMediaLinks:List&amp;lt;string&amp;gt;" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="120" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-11" value="AgeRestricted:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="150" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-12" value="PrepaymentRequired:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="180" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-14" value="AgeRestricted:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="210" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-15" value="Suspended:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="240" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-16" value="SuspendedDate:date&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="270" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-17" value="UnsuspendDate:date&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="300" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-18" value="SuspendReason:string&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="330" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-19" value="SuspendAdminId:string&amp;nbsp; &amp;nbsp;nullable" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="yIItKk7yf84a2ZR0tYBl-6">
<mxGeometry y="360" width="175" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="yIItKk7yf84a2ZR0tYBl-7" target="UY2qzOe5k0s0eoKJkEdN-20">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-18" value="SellerService" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="590" y="660" width="190" height="210" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-19" value="PK Id:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-18" vertex="1">
<mxGeometry y="30" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-20" value="FK SellerProfileId:int" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-18" vertex="1">
<mxGeometry y="60" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-21" value="Name:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-18" vertex="1">
<mxGeometry y="90" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-22" value="Description:string" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-18" vertex="1">
<mxGeometry y="120" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-23" value="Price:double" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-18" vertex="1">
<mxGeometry y="150" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="UY2qzOe5k0s0eoKJkEdN-24" value="Archived:bool" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" parent="UY2qzOe5k0s0eoKJkEdN-18" vertex="1">
<mxGeometry y="180" width="190" height="30" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="yIItKk7yf84a2ZR0tYBl-7" target="UY2qzOe5k0s0eoKJkEdN-29">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="560" y="735" />
<mxPoint x="560" y="515" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-19" target="UY2qzOe5k0s0eoKJkEdN-28">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-29" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="yIItKk7yf84a2ZR0tYBl-7" target="UY2qzOe5k0s0eoKJkEdN-64">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-30" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-63" target="yIItKk7yf84a2ZR0tYBl-8">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="270" y="315" />
<mxPoint x="270" y="765" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-63" target="UY2qzOe5k0s0eoKJkEdN-27">
<mxGeometry relative="1" as="geometry">
<mxPoint x="380" y="390" as="targetPoint" />
<Array as="points">
<mxPoint x="320" y="315" />
<mxPoint x="320" y="455" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-19" target="UY2qzOe5k0s0eoKJkEdN-55">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1070" y="705" />
<mxPoint x="1070" y="440" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-33" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-26" target="UY2qzOe5k0s0eoKJkEdN-57">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1090" y="425" />
<mxPoint x="1090" y="425" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-36" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-63" target="UY2qzOe5k0s0eoKJkEdN-59">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="315" />
<mxPoint x="320" y="360" />
<mxPoint x="1050" y="360" />
<mxPoint x="1050" y="395" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-63" target="UY2qzOe5k0s0eoKJkEdN-38">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="1050" y="315" />
<mxPoint x="1050" y="185" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-39" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-26" target="UY2qzOe5k0s0eoKJkEdN-37">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-40" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-36" target="UY2qzOe5k0s0eoKJkEdN-43">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-41" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-63" target="UY2qzOe5k0s0eoKJkEdN-14">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-43" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="UY2qzOe5k0s0eoKJkEdN-20" target="UY2qzOe5k0s0eoKJkEdN-5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="yIItKk7yf84a2ZR0tYBl-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="yIItKk7yf84a2ZR0tYBl-7" target="UY2qzOe5k0s0eoKJkEdN-4">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="560" y="735" />
<mxPoint x="560" y="985" />
</Array>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

221
docs/userflow_design.drawio Normal file
View File

@ -0,0 +1,221 @@
<mxfile host="Electron" modified="2024-01-29T05:26:23.308Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.2 Chrome/114.0.5735.289 Electron/25.9.4 Safari/537.36" etag="8Vd_Z-8mX0rej4oU3sUG" version="22.1.2" type="device">
<diagram name="Page-1" id="wG4NdPAZgkJ58mmuEcac">
<mxGraphModel dx="2895" dy="698" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="k-1lLaBYFCKJAabm89fw-5" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-1" target="k-1lLaBYFCKJAabm89fw-4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-1" value="User" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="60" y="300" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-4" target="k-1lLaBYFCKJAabm89fw-8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-19" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-4" target="k-1lLaBYFCKJAabm89fw-18">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-4" value="Landing Page" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="140" y="318" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-8" target="k-1lLaBYFCKJAabm89fw-10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-8" value="Discover Artists" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="340" y="318" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-10" target="k-1lLaBYFCKJAabm89fw-8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-10" target="k-1lLaBYFCKJAabm89fw-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-10" value="View Artist" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="340" y="423" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-13" target="k-1lLaBYFCKJAabm89fw-10">
<mxGeometry relative="1" as="geometry">
<mxPoint x="340" y="450" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-13" target="k-1lLaBYFCKJAabm89fw-23">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-13" value="View Service" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="340" y="528" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-21" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-18" target="k-1lLaBYFCKJAabm89fw-20">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-18" target="k-1lLaBYFCKJAabm89fw-8">
<mxGeometry relative="1" as="geometry">
<mxPoint x="-260" y="70" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-26" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-18" target="k-1lLaBYFCKJAabm89fw-25">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-18" value="Login" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="140" y="423" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-28" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-20" target="k-1lLaBYFCKJAabm89fw-27">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-20" value="View Orders" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="140" y="528" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-29" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-23" target="k-1lLaBYFCKJAabm89fw-27">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-23" value="Purchase Service" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="340" y="633" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-44" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-25" target="k-1lLaBYFCKJAabm89fw-43">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-50" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-25" target="k-1lLaBYFCKJAabm89fw-49">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-52" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-25" target="k-1lLaBYFCKJAabm89fw-51">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-63" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-25" target="k-1lLaBYFCKJAabm89fw-62">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-25" value="View Seller Dashboard" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-447" y="423" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-31" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-27" target="k-1lLaBYFCKJAabm89fw-30">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-34" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-27" target="k-1lLaBYFCKJAabm89fw-33">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-36" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-27" target="k-1lLaBYFCKJAabm89fw-35">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-27" target="k-1lLaBYFCKJAabm89fw-37">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-41" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-27" target="k-1lLaBYFCKJAabm89fw-40">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-42" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-27" target="k-1lLaBYFCKJAabm89fw-39">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-84" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-27" target="k-1lLaBYFCKJAabm89fw-83">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-27" value="View Order" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="140" y="633" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-71" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-30" target="k-1lLaBYFCKJAabm89fw-70">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-30" value="Send Message To Seller" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="121" y="721" width="160" height="32" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-33" value="Cancel Order" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-60" y="630" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-35" value="Accept Revision" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-60" y="660" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-37" value="Pay For Order" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-60" y="600" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-39" value="Accept Order Terms" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-60" y="570" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-40" value="Deny Revision" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-60" y="691" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-43" value="Manage Payout Settings" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-341" y="317.5" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-49" value="View Balance &amp;amp; Payout Date" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-569" y="318" width="164" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-54" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-51" target="k-1lLaBYFCKJAabm89fw-53">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-51" value="View Services" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-667" y="423" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-58" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-53" target="k-1lLaBYFCKJAabm89fw-57">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-61" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-53" target="k-1lLaBYFCKJAabm89fw-60">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-53" value="View Service" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-667" y="519" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-57" value="Delete Service" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-667" y="627" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-60" value="Edit Service Details&lt;br&gt;(price,name,description)" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-918" y="510" width="140" height="43" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-65" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-62" target="k-1lLaBYFCKJAabm89fw-64">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-62" value="View Orders" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-447" y="528" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-67" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-64" target="k-1lLaBYFCKJAabm89fw-66">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-78" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-64" target="k-1lLaBYFCKJAabm89fw-76">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-79" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-64" target="k-1lLaBYFCKJAabm89fw-75">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-80" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-64" target="k-1lLaBYFCKJAabm89fw-73">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-81" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-64" target="k-1lLaBYFCKJAabm89fw-74">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-82" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-64" target="k-1lLaBYFCKJAabm89fw-77">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-64" value="View Order" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-447" y="630" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-69" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;" edge="1" parent="1" source="k-1lLaBYFCKJAabm89fw-66" target="k-1lLaBYFCKJAabm89fw-68">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-66" value="Send Message To Buyer" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-447" y="721" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-68" value="Upload Attachment" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-447" y="814" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-70" value="Upload Attachment" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="130" y="814" width="140" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-73" value="Adjust Price" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-200" y="629.5" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-74" value="Complete Revision" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-200" y="659.5" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-75" value="Start Order" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-200" y="599.5" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-76" value="Accept Order" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-200" y="569.5" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-77" value="Cancel Order" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-200" y="690.5" width="120" height="25" as="geometry" />
</mxCell>
<mxCell id="k-1lLaBYFCKJAabm89fw-83" value="Leave Review" style="whiteSpace=wrap;html=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="-60" y="722.5" width="120" height="25" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

25
src/.dockerignore Normal file
View File

@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

View File

@ -0,0 +1,370 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoGeneratedRunConfigurationManager">
<projectFile profileName="http">ArtPlatform.API/ArtPlatform.API.csproj</projectFile>
<projectFile profileName="https">ArtPlatform.API/ArtPlatform.API.csproj</projectFile>
<projectFile profileName="http">ArtPlatform.App/ArtPlatform.App.csproj</projectFile>
<projectFile profileName="https">ArtPlatform.App/ArtPlatform.App.csproj</projectFile>
<projectFile>ArtPlatform.Database.Migrator/ArtPlatform.Database.Migrator.csproj</projectFile>
<projectFile profileName="https">ArtPlatform.UI/ArtPlatform.UI.csproj</projectFile>
<projectFile profileName="http">ArtPlatform.WebApp/ArtPlatform.WebApp.csproj</projectFile>
<projectFile profileName="https">ArtPlatform.WebApp/ArtPlatform.WebApp.csproj</projectFile>
<projectFile profileName="http">ArtPlatform.WebApplication/ArtPlatform.WebApplication.csproj</projectFile>
<projectFile profileName="https">ArtPlatform.WebApplication/ArtPlatform.WebApplication.csproj</projectFile>
<projectFile profileName="http">ArtPlatform/ArtPlatform.csproj</projectFile>
<projectFile profileName="https">ArtPlatform/ArtPlatform.csproj</projectFile>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="e251c08f-6fd2-4319-a6ec-5f03319157f3" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/.dockerignore" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/.idea.ArtPlatform.Database/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/ApplicationDatabaseConfigurationModel.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/ApplicationDbContext.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/ArtPlatform.Database.csproj" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/SellerConfiguredSetting.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/SellerProfilePortfolioPiece.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/SellerService.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/SellerServiceOrder.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/SellerServiceOrderMessage.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/SellerServiceOrderMessageAttachment.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/SellerServiceOrderReview.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/SellerSetting.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/User.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Entities/UserSellerProfile.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.Database/Enums/EnumOrderStatus.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ArtPlatform.sln" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/.dockerignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/.idea/.idea.ArtPlatform/.idea/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/.idea/.idea.ArtPlatform/.idea/aws.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/.idea/.idea.ArtPlatform/.idea/encodings.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/.idea/.idea.ArtPlatform/.idea/indexLayout.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/.idea/.idea.ArtPlatform/.idea/vcs.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlaform.DAL/ArtPlaform.DAL.csproj" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlaform.DAL/Class1.cs" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.Api/ArtPlatform.Api.csproj" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.Api/Controllers/WeatherForecastController.cs" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.Api/Dockerfile" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.Api/Program.cs" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.Api/Properties/launchSettings.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.Api/WeatherForecast.cs" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.Api/appsettings.Development.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.Api/appsettings.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.DatabaseUpdater/ArtPlatform.DatabaseUpdater.csproj" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.DatabaseUpdater/Dockerfile" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.DatabaseUpdater/Program.cs" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../ArtPlatform/ArtPlatform.sln" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../Damien.ArtPlatform/.idea/.idea.Damien.ArtPlatform/.idea/aws.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../Damien.ArtPlatform/.idea/.idea.Damien.ArtPlatform/.idea/indexLayout.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../Damien.ArtPlatform/.idea/.idea.Damien.ArtPlatform/.idea/projectSettingsUpdater.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../Damien.ArtPlatform/.idea/.idea.Damien.ArtPlatform/.idea/vcs.xml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/../Damien.ArtPlatform/.idea/.idea.Damien.ArtPlatform/.idea/workspace.xml" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 8
}]]></component>
<component name="ProjectId" id="2bVSVHjAxjbTSogi69zTRZwrMUs" />
<component name="ProjectLevelVcsManager">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
".NET Launch Settings Profile.ArtPlatform.API: http.executor": "Run",
".NET Launch Settings Profile.ArtPlatform.App: http.executor": "Run",
".NET Launch Settings Profile.ArtPlatform.WebApp: http.executor": "Run",
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"git-widget-placeholder": "main",
"last_opened_file_path": "/home/damienostler/Documents/Github Repositories/art_platform/ArtPlatform.Database/ArtPlatform/ArtPlatform.sln",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.pluginManager",
"vue.rearranger.settings.migration": "true"
},
"keyToStringList": {
"DatabaseDriversLRU": [
"postgresql"
],
"rider.external.source.directories": [
"/home/damienostler/.config/JetBrains/Rider2023.3/resharper-host/DecompilerCache",
"/home/damienostler/.config/JetBrains/Rider2023.3/resharper-host/SourcesCache",
"/home/damienostler/.local/share/Symbols/src"
]
}
}]]></component>
<component name="RunManager" selected=".NET Launch Settings Profile.ArtPlatform.App: http">
<configuration name="ArtPlatform.Database.Migrator" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/ArtPlatform.Database.Migrator/ArtPlatform.Database.Migrator.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="ArtPlatform: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/ArtPlatform/ArtPlatform.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net8.0" />
<option name="LAUNCH_PROFILE_NAME" value="http" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="ArtPlatform: https" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/ArtPlatform/ArtPlatform.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net8.0" />
<option name="LAUNCH_PROFILE_NAME" value="https" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="ArtPlatform.API: https" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/ArtPlatform.API/ArtPlatform.API.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net8.0" />
<option name="LAUNCH_PROFILE_NAME" value="https" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="ArtPlatform.App: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/ArtPlatform.App/ArtPlatform.App.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net8.0" />
<option name="LAUNCH_PROFILE_NAME" value="http" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="ArtPlatform.App: https" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/ArtPlatform.App/ArtPlatform.App.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net8.0" />
<option name="LAUNCH_PROFILE_NAME" value="https" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="ArtPlatform.WebApplication: https" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/ArtPlatform.WebApplication/ArtPlatform.WebApplication.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net8.0" />
<option name="LAUNCH_PROFILE_NAME" value="https" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="ArtPlatform.API/Dockerfile" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="artplatform.api" />
<option name="containerName" value="artplatform.api" />
<option name="contextFolderPath" value="$PROJECT_DIR$" />
<option name="portBindings">
<list>
<DockerPortBindingImpl>
<option name="containerPort" value="8080" />
<option name="hostIp" value="127.0.0.1" />
<option name="hostPort" value="8080" />
</DockerPortBindingImpl>
</list>
</option>
<option name="sourceFilePath" value="ArtPlatform.API/Dockerfile" />
</settings>
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" />
</configuration>
<configuration name="ArtPlatform.App/Dockerfile" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="artplatform.app" />
<option name="containerName" value="artplatform.app" />
<option name="contextFolderPath" value="$PROJECT_DIR$" />
<option name="portBindings">
<list>
<DockerPortBindingImpl>
<option name="containerPort" value="8080" />
<option name="hostIp" value="127.0.0.1" />
<option name="hostPort" value="8080" />
</DockerPortBindingImpl>
</list>
</option>
<option name="sourceFilePath" value="ArtPlatform.App/Dockerfile" />
</settings>
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" />
</configuration>
<configuration name="ArtPlatform/Dockerfile" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="artplatform" />
<option name="containerName" value="artplatform" />
<option name="contextFolderPath" value="$PROJECT_DIR$" />
<option name="portBindings">
<list>
<DockerPortBindingImpl>
<option name="containerPort" value="8080" />
<option name="hostIp" value="127.0.0.1" />
<option name="hostPort" value="8080" />
</DockerPortBindingImpl>
</list>
</option>
<option name="sourceFilePath" value="ArtPlatform/Dockerfile" />
</settings>
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" />
</configuration>
<configuration name="ArtPlatform.UI/Dockerfile" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="artplatform.ui" />
<option name="containerName" value="artplatform.ui" />
<option name="contextFolderPath" value="$PROJECT_DIR$" />
<option name="portBindings">
<list>
<DockerPortBindingImpl>
<option name="containerPort" value="8080" />
<option name="hostIp" value="127.0.0.1" />
<option name="hostPort" value="8080" />
</DockerPortBindingImpl>
</list>
</option>
<option name="sourceFilePath" value="ArtPlatform.UI/Dockerfile" />
</settings>
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" />
</configuration>
<configuration name="ArtPlatform.WebApp/Dockerfile" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="artplatform.webapp" />
<option name="containerName" value="artplatform.webapp" />
<option name="contextFolderPath" value="$PROJECT_DIR$" />
<option name="portBindings">
<list>
<DockerPortBindingImpl>
<option name="containerPort" value="8080" />
<option name="hostIp" value="127.0.0.1" />
<option name="hostPort" value="8080" />
</DockerPortBindingImpl>
</list>
</option>
<option name="sourceFilePath" value="ArtPlatform.WebApp/Dockerfile" />
</settings>
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" />
</configuration>
<configuration name="ArtPlatform.WebApplication/Dockerfile" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="artplatform.webapplication" />
<option name="containerName" value="artplatform.webapplication" />
<option name="contextFolderPath" value="$PROJECT_DIR$" />
<option name="portBindings">
<list>
<DockerPortBindingImpl>
<option name="containerPort" value="8080" />
<option name="hostIp" value="127.0.0.1" />
<option name="hostPort" value="8080" />
</DockerPortBindingImpl>
</list>
</option>
<option name="sourceFilePath" value="ArtPlatform.WebApplication/Dockerfile" />
</settings>
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" />
</configuration>
<configuration default="true" type="docker-deploy" factoryName="dockerfile" temporary="true">
<deployment type="dockerfile" />
<method v="2" />
</configuration>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="e251c08f-6fd2-4319-a6ec-5f03319157f3" name="Changes" comment="" />
<created>1706298775013</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1706298775013</updated>
<workItem from="1706298780176" duration="10198000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
</component>
</project>

View File

@ -0,0 +1,2 @@
#n:public
!<md> [1314, 0, null, null, -2147483648, -2147483648]

View File

@ -0,0 +1,271 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoGeneratedRunConfigurationManager">
<projectFile profileName="http">ArtPlatform.API/ArtPlatform.API.csproj</projectFile>
<projectFile profileName="https">ArtPlatform.API/ArtPlatform.API.csproj</projectFile>
<projectFile>ArtPlatform.Database.Migrator/ArtPlatform.Database.Migrator.csproj</projectFile>
<projectFile profileName="http">ArtPlatform/ArtPlatform.csproj</projectFile>
<projectFile profileName="https">ArtPlatform/ArtPlatform.csproj</projectFile>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="e251c08f-6fd2-4319-a6ec-5f03319157f3" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/ui/components/header.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/ui/components/header.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ui/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/ui/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ui/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/ui/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ui/pages/_app.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/ui/pages/_app.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ui/pages/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/ui/pages/index.tsx" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="HighlightingSettingsPerFile">
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/6fb07e288afa4fb5b6e5c73ac90727b21dea00/d1/1e296c5d/ActionMethodExecutor.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/ArtPlatform.API/react/package-lock.json" root0="SKIP_HIGHLIGHTING" />
</component>
<component name="KubernetesApiProvider">{
&quot;contexts&quot;: [
{
&quot;name&quot;: &quot;minikube&quot;,
&quot;originalNamespace&quot;: &quot;default&quot;
}
],
&quot;isMigrated&quot;: true
}</component>
<component name="KubernetesSettings">
<option name="contextName" value="minikube" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 8
}</component>
<component name="ProjectId" id="2bVSVHjAxjbTSogi69zTRZwrMUs" />
<component name="ProjectLevelVcsManager">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;.NET Launch Settings Profile.ArtPlatform.API: https.executor&quot;: &quot;Run&quot;,
&quot;.NET Project.ArtPlatform.Database.Migrator.executor&quot;: &quot;Run&quot;,
&quot;Docker.ArtPlatform.API/Dockerfile.executor&quot;: &quot;Run&quot;,
&quot;HTTP Request.All in generated-requests.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/damienostler/Documents/Github Repositories/art_platform/src/ArtPlatform.Database/ArtPlatform.Database.csproj&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
&quot;ts.external.directory.path&quot;: &quot;/home/damienostler/Documents/Github Repositories/art_platform/src/ArtPlatform.API/react/node_modules/typescript/lib&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;postgresql&quot;
],
&quot;rider.external.source.directories&quot;: [
&quot;/home/damienostler/.config/JetBrains/Rider2023.3/resharper-host/DecompilerCache&quot;,
&quot;/home/damienostler/.config/JetBrains/Rider2023.3/resharper-host/SourcesCache&quot;,
&quot;/home/damienostler/.local/share/Symbols/src&quot;
]
}
}</component>
<component name="RunManager" selected=".NET Launch Settings Profile.ArtPlatform.API: https">
<configuration name="ArtPlatform.Database.Migrator" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/comissions.app.database.migrator/comissions.app.database.migrator.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="All in generated-requests" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" temporary="true" path="$APPLICATION_CONFIG_DIR$/scratches/generated-requests.http">
<method v="2" />
</configuration>
<configuration name="ArtPlatform.API: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/comissions.app.api/comissions.app.api.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net8.0" />
<option name="LAUNCH_PROFILE_NAME" value="http" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration name="ArtPlatform.API: https" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/comissions.app.api/comissions.app.api.csproj" />
<option name="LAUNCH_PROFILE_TFM" value="net8.0" />
<option name="LAUNCH_PROFILE_NAME" value="https" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="GENERATE_APPLICATIONHOST_CONFIG" value="1" />
<option name="SHOW_IIS_EXPRESS_OUTPUT" value="0" />
<option name="SEND_DEBUG_REQUEST" value="1" />
<option name="ADDITIONAL_IIS_EXPRESS_ARGUMENTS" value="" />
<method v="2">
<option name="Build" />
</method>
</configuration>
<configuration default="true" type="docker-deploy" factoryName="dockerfile" temporary="true">
<deployment type="dockerfile">
<settings />
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" />
</configuration>
<configuration name="comissions.app.api/Dockerfile: Compose Deployment" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="artplatform.api" />
<option name="containerName" value="artplatform.api" />
<option name="contextFolderPath" value="$PROJECT_DIR$" />
<option name="portBindings">
<list>
<DockerPortBindingImpl>
<option name="containerPort" value="8080" />
<option name="hostIp" value="127.0.0.1" />
<option name="hostPort" value="8080" />
</DockerPortBindingImpl>
</list>
</option>
<option name="sourceFilePath" value="comissions.app.api/Dockerfile" />
</settings>
</deployment>
<EXTENSION ID="com.jetbrains.rider.docker.debug" isFastModeEnabled="true" isSslEnabled="false" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="HTTP Request.All in generated-requests" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="e251c08f-6fd2-4319-a6ec-5f03319157f3" name="Changes" comment="" />
<created>1706298775013</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1706298775013</updated>
<workItem from="1706298780176" duration="5462000" />
<workItem from="1706328737604" duration="1191000" />
<workItem from="1706329945610" duration="25743000" />
<workItem from="1706356001506" duration="934000" />
<workItem from="1706356953689" duration="115000" />
<workItem from="1706361173670" duration="1284000" />
<workItem from="1706391729848" duration="34425000" />
<workItem from="1706435225468" duration="742000" />
<workItem from="1706437850699" duration="2805000" />
<workItem from="1706475738092" duration="3224000" />
<workItem from="1706492338852" duration="2344000" />
<workItem from="1706502030301" duration="606000" />
<workItem from="1706504263000" duration="1104000" />
<workItem from="1706538019385" duration="1260000" />
<workItem from="1706569829363" duration="2355000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/comissions.app.api/Controllers/SellerProfileController.cs</url>
<line>143</line>
<properties documentPath="$PROJECT_DIR$/ArtPlatform.API/Controllers/SellerProfileController.cs" initialLine="97" containingFunctionPresentation="Method 'AddPortfolio'">
<startOffsets>
<option value="6590" />
</startOffsets>
<endOffsets>
<option value="6624" />
</endOffsets>
</properties>
<option name="timeStamp" value="12" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/comissions.app.api/Controllers/SellerProfileController.cs</url>
<line>141</line>
<properties documentPath="$PROJECT_DIR$/ArtPlatform.API/Controllers/SellerProfileController.cs" initialLine="95" containingFunctionPresentation="Method 'AddPortfolio'">
<startOffsets>
<option value="6413" />
</startOffsets>
<endOffsets>
<option value="6443" />
</endOffsets>
</properties>
<option name="timeStamp" value="13" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/comissions.app.api/Controllers/OrderController.cs</url>
<line>60</line>
<properties documentPath="$PROJECT_DIR$/ArtPlatform.API/Controllers/OrderController.cs" initialLine="60" containingFunctionPresentation="Method 'ProcessWebhookEvent'">
<startOffsets>
<option value="2514" />
</startOffsets>
<endOffsets>
<option value="2563" />
</endOffsets>
</properties>
<option name="timeStamp" value="29" />
</line-breakpoint>
<line-breakpoint enabled="true" type="DotNet Breakpoints">
<url>file://$PROJECT_DIR$/comissions.app.api/Controllers/OrderController.cs</url>
<line>47</line>
<properties documentPath="$PROJECT_DIR$/ArtPlatform.API/Controllers/OrderController.cs" initialLine="47" containingFunctionPresentation="Method 'ProcessWebhookEvent'">
<startOffsets>
<option value="1951" />
</startOffsets>
<endOffsets>
<option value="2010" />
</endOffsets>
</properties>
<option name="timeStamp" value="31" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
<pin-to-top-manager>
<pinned-members>
<PinnedItemInfo parentTag="Type#System.Security.Claims.Claim" memberName="Subject" />
<PinnedItemInfo parentTag="Type#System.Security.Claims.ClaimsIdentity" memberName="Claims" />
</pinned-members>
</pin-to-top-manager>
</component>
</project>

13
src/.idea/.idea.comissions.app/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/contentModel.xml
/projectSettingsUpdater.xml
/.idea.ArtPlatform.Database.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1 @@
comissions.app

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="accountSettings">
<option name="activeProfile" value="profile:default" />
<option name="activeRegion" value="us-east-1" />
<option name="recentlyUsedProfiles">
<list>
<option value="profile:default" />
</list>
</option>
<option name="recentlyUsedRegions">
<list>
<option value="us-east-1" />
</list>
</option>
</component>
<component name="explorerToolWindow">
<option name="selectedTab" value="Amazon Q + CodeWhisperer" />
</component>
</project>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="postgres@localhost" uuid="f139dccd-eb47-4ad5-bb4f-f457f16ec1b7">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/postgres</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="artplatform@localhost" uuid="e4a3d98b-bfa1-4036-a59a-24d9e362740e">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/artplatform</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,11 @@
const namespace = 'http://schemas.microsoft.com/ws/2008/06/identity/claims';
exports.onExecutePostLogin = async (event, api) => {{
api.accessToken.setCustomClaim(namespace+'/emailaddress', event.user.email);
api.accessToken.setCustomClaim(namespace+'/name', event.user.nickname);
var assignedRoles = event.authorization?.roles;
if(assignedRoles!=null && assignedRoles.length>0){
for(var role in assignedRoles){
api.accessToken.setCustomClaim(namespace+'role', assignedRoles[role]);
}
}
}}

View File

@ -0,0 +1,96 @@
using comissions.app.api.Extensions;
using ArtPlatform.Database;
using ArtPlatform.Database.Entities;
using comissions.app.database;
using comissions.app.database.Entities;
using comissions.app.database.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Controllers;
[ApiController]
[Authorize("admin")]
[Route("api/admin/[controller]")]
public class AdminOrdersController:ControllerBase
{
private readonly ApplicationDbContext _dbContext;
public AdminOrdersController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
public async Task<IActionResult> GetOrders(string search="", int offset = 0, int pageSize = 10)
{
var orders = _dbContext.SellerServiceOrders.Include(x=>x.Seller).ThenInclude(x=>x.User).Include(x=>x.Buyer)
.Where(x=>x.Seller.User.DisplayName.Contains(search)
|| x.Seller.User.Email.Contains(search)
|| x.Buyer.DisplayName.Contains(search)
|| x.Buyer.Email.Contains(search))
.Skip(offset).Take(pageSize).ToList();
return Ok(orders);
}
[HttpGet("Count")]
public async Task<IActionResult> GetOrdersCount(string search="")
{
var result = _dbContext.SellerServiceOrders.Include(x=>x.Seller).ThenInclude(x=>x.User).Include(x=>x.Buyer)
.Where(x=>x.Seller.User.DisplayName.Contains(search)
|| x.Seller.User.Email.Contains(search)
|| x.Buyer.DisplayName.Contains(search)
|| x.Buyer.Email.Contains(search))
.Count();
return Ok(result);
}
[HttpGet("{orderId:int}")]
public async Task<IActionResult> GetOrder(int orderId)
{
var order = await _dbContext.SellerServiceOrders.Include(x=>x.Seller).ThenInclude(x=>x.User).Include(x=>x.Buyer)
.FirstOrDefaultAsync(x=>x.Id==orderId);
if (order == null)
return NotFound("Order not found.");
return Ok(order);
}
[HttpPost("{orderId:int}")]
public async Task<IActionResult> SendMessage(int orderId, [FromBody]string message)
{
var order = await _dbContext.SellerServiceOrders.Include(x=>x.Seller).ThenInclude(x=>x.User).Include(x=>x.Buyer)
.FirstOrDefaultAsync(x=>x.Id==orderId);
if (order == null)
return NotFound("Order not found.");
order.Messages.Add(new SellerServiceOrderMessage()
{
Message = message,
SenderId = User.GetUserId(),
SentAt = DateTime.UtcNow
});
_dbContext.SellerServiceOrders.Update(order);
await _dbContext.SaveChangesAsync();
return Ok(order);
}
[HttpPut("{orderId:int}/Terminate")]
public async Task<IActionResult> TerminateOrder(int orderId)
{
var order = await _dbContext.SellerServiceOrders.Include(x=>x.Seller).ThenInclude(x=>x.User).Include(x=>x.Buyer)
.FirstOrDefaultAsync(x=>x.Id==orderId);
if (order == null)
return NotFound("Order not found.");
order.Status = EnumOrderStatus.Cancelled;
_dbContext.SellerServiceOrders.Update(order);
await _dbContext.SaveChangesAsync();
return Ok(order);
}
}

View File

@ -0,0 +1,86 @@
using comissions.app.api.Models.SellerProfileRequest;
using ArtPlatform.Database;
using ArtPlatform.Database.Entities;
using comissions.app.database;
using comissions.app.database.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Controllers;
[ApiController]
[Authorize("admin")]
[Route("api/admin/[controller]")]
public class AdminSellerRequestsController : Controller
{
private readonly ApplicationDbContext _dbContext;
public AdminSellerRequestsController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
/// <summary>
/// Gets a list of all of the requests from users to become a seller.
/// </summary>
/// <param name="offset"> The offset to start at.</param>
/// <param name="pageSize"> The amount of records to return.</param>
/// <returns>A list of seller profile requests</returns>
[HttpGet]
[Authorize("read:seller-profile-request")]
public async Task<IActionResult> GetSellerRequests(int offset = 0, int pageSize = 10)
{
var requests = _dbContext.SellerProfileRequests.Skip(offset).Take(pageSize).ToList();
var result = requests.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
/// <summary>
/// Gets the amount of requests there are from users to become a seller.
/// </summary>
/// <returns>The number of requests.</returns>
[HttpGet]
[Authorize("read:seller-profile-request")]
[Route("Count")]
public async Task<IActionResult> GetSellerRequestsCount()
{
var result = _dbContext.SellerProfileRequests.Count();
return Ok(result);
}
/// <summary>
/// Accepts a request to become a seller from a user.
/// </summary>
/// <param name="userId">The ID of the user to accept the request for.</param>
/// <returns>The new seller profile.</returns>
[HttpPut]
[Authorize("write:seller-profile-request")]
[Route("{userId}")]
public async Task<IActionResult> AcceptSellerRequest(string userId)
{
var request = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId);
if(request==null)
return NotFound("No request for that user exists.");
if (request.Accepted == true)
return BadRequest("User is already a seller.");
request.Accepted = true;
request.AcceptedDate = DateTime.UtcNow;
var newSellerProfile = new UserSellerProfile()
{
UserId = userId,
AgeRestricted = false,
Biography = string.Empty,
SocialMediaLinks = new List<string>(){}
};
_dbContext.UserSellerProfiles.Add(newSellerProfile);
request = _dbContext.SellerProfileRequests.Update(request).Entity;
await _dbContext.SaveChangesAsync();
var result = request.ToModel();
return Ok(result);
}
}

View File

@ -0,0 +1,142 @@
using comissions.app.api.Extensions;
using ArtPlatform.Database;
using comissions.app.database;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Controllers;
[ApiController]
[Authorize("admin")]
[Route("api/admin/[controller]")]
public class AdminSellersController:ControllerBase
{
private readonly ApplicationDbContext _dbContext;
public AdminSellersController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
public async Task<IActionResult> GetSellers(string search="", int offset = 0, int pageSize = 10)
{
var sellers = await _dbContext.UserSellerProfiles.Include(x=>x.User)
.Where(x=>x.User.DisplayName.Contains(search) || x.User.Email.Contains(search))
.Skip(offset).Take(pageSize).ToListAsync();
return Ok(sellers);
}
[HttpGet("Count")]
public async Task<IActionResult> GetSellersCount(string search="")
{
var result = await _dbContext.UserSellerProfiles.Include(x=>x.User)
.Where(x=>x.User.DisplayName.Contains(search) || x.User.Email.Contains(search))
.CountAsync();
return Ok(result);
}
[HttpGet("{sellerId:int}")]
public async Task<IActionResult> GetSeller(int sellerId)
{
var seller = await _dbContext.UserSellerProfiles.Include(x=>x.User)
.FirstOrDefaultAsync(x=>x.Id==sellerId);
if (seller == null)
return NotFound("Seller not found.");
return Ok(seller);
}
[HttpGet("{sellerId:int}/Orders")]
public async Task<IActionResult> GetSellerOrders(int sellerId)
{
var seller = _dbContext.UserSellerProfiles.Include(x=>x.User)
.FirstOrDefault(x=>x.Id==sellerId);
if (seller == null)
return NotFound("Seller not found.");
var orders = await _dbContext.SellerServiceOrders.Where(x=>x.SellerId==sellerId).ToListAsync();
return Ok(orders);
}
[HttpPut("{sellerId:int}/Suspend")]
public async Task<IActionResult> SuspendSeller(int sellerId, [FromQuery]string reason, [FromQuery]int days)
{
var seller = _dbContext.UserSellerProfiles.FirstOrDefault(x=>x.Id==sellerId);
if (seller == null)
return NotFound("Seller not found.");
if (seller.Suspended)
return BadRequest("Seller is already suspended.");
seller.Suspended = true;
seller.SuspendedDate = DateTime.UtcNow;
seller.UnsuspendDate = DateTime.UtcNow.AddDays(days);
seller.SuspendedReason = reason;
seller.SuspendAdminId = User.GetUserId();
_dbContext.UserSellerProfiles.Update(seller);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPut("{sellerId:int}/Unsuspend")]
public async Task<IActionResult> UnsuspendSeller(int sellerId)
{
var seller = _dbContext.UserSellerProfiles.FirstOrDefault(x=>x.Id==sellerId);
if (seller == null)
return NotFound("Seller not found.");
if (!seller.Suspended)
return BadRequest("Seller is not suspended.");
seller.Suspended = false;
seller.SuspendedDate = null;
seller.UnsuspendDate = null;
seller.SuspendedReason = null;
seller.SuspendAdminId = null;
_dbContext.UserSellerProfiles.Update(seller);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPut("{sellerId:int}/Terminate")]
public async Task<IActionResult> TerminateSeller(int sellerId)
{
var seller = _dbContext.UserSellerProfiles.FirstOrDefault(x=>x.Id==sellerId);
if (seller == null)
return NotFound("Seller not found.");
if (!seller.Suspended)
return BadRequest("Seller is not suspended.");
_dbContext.UserSellerProfiles.Remove(seller);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPut("{sellerId:int}/SetBiography")]
public async Task<IActionResult> SetBiography(int sellerId, [FromBody]string biography)
{
var seller = _dbContext.UserSellerProfiles.FirstOrDefault(x=>x.Id==sellerId);
if (seller == null)
return NotFound("Seller not found.");
if (!seller.Suspended)
return BadRequest("Seller is not suspended.");
seller.Biography = biography;
_dbContext.UserSellerProfiles.Update(seller);
await _dbContext.SaveChangesAsync();
return Ok();
}
}

View File

@ -0,0 +1,162 @@
using comissions.app.api.Extensions;
using ArtPlatform.Database;
using comissions.app.database;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Controllers;
[ApiController]
[Authorize("admin")]
[Route("api/admin/[controller]")]
public class AdminUsersController:ControllerBase
{
private readonly ApplicationDbContext _dbContext;
public AdminUsersController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
public async Task<IActionResult> GetUsers(string search="", int offset = 0, int pageSize = 10)
{
var users = await _dbContext.Users
.Where(x=>x.DisplayName.Contains(search) || x.Email.Contains(search))
.Skip(offset).Take(pageSize).ToListAsync();
return Ok(users);
}
[HttpGet("Count")]
public async Task<IActionResult> GetUsersCount(string search="")
{
var result = await _dbContext.Users
.Where(x=>x.DisplayName.Contains(search) || x.Email.Contains(search))
.CountAsync();
return Ok(result);
}
[HttpGet("{userId}")]
public async Task<IActionResult> GetUser(string userId)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId);
if (user == null)
return NotFound("User not found.");
return Ok(user);
}
[HttpGet("{userId}/Orders")]
public async Task<IActionResult> GetUserOrders(string userId)
{
var user = await _dbContext.Users.Include(x=>x.Orders).FirstOrDefaultAsync(x=>x.Id==userId);
if (user == null)
return NotFound("User not found.");
return Ok(user.Orders);
}
[HttpPut("{userId}/Suspend")]
public async Task<IActionResult> SuspendUser(string userId, [FromQuery]string reason, [FromQuery]int days)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId);
if (user == null)
return NotFound("User not found.");
user.Suspended = true;
user.SuspendedDate = DateTime.UtcNow;
user.SuspendedReason = reason;
user.SuspendAdminId = User.GetUserId();
user.UnsuspendDate = DateTime.UtcNow.AddDays(days);
_dbContext.Users.Update(user);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPut("{userId}/Unsuspend")]
public async Task<IActionResult> UnsuspendUser(string userId)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId);
if (user == null)
return NotFound("User not found.");
user.Suspended = false;
user.SuspendedDate = null;
user.SuspendedReason = null;
user.SuspendAdminId = null;
user.UnsuspendDate = null;
_dbContext.Users.Update(user);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPut("{userId}/Ban")]
public async Task<IActionResult> BanUser(string userId, [FromQuery]string reason, [FromQuery]int days)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId);
if (user == null)
return NotFound("User not found.");
user.Banned = true;
user.BannedDate = DateTime.UtcNow;
user.BannedReason = reason;
user.BanAdminId = User.GetUserId();
user.UnbanDate = DateTime.UtcNow.AddDays(days);
_dbContext.Users.Update(user);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPut("{userId}/Unban")]
public async Task<IActionResult> UnbanUser(string userId)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId);
if (user == null)
return NotFound("User not found.");
user.Banned = false;
user.BannedDate = null;
user.BannedReason = null;
user.BanAdminId = null;
user.UnbanDate = null;
_dbContext.Users.Update(user);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPut("{userId}/SetDisplayName")]
public async Task<IActionResult> SetDisplayName(string userId, [FromBody]string displayName)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId);
if (user == null)
return NotFound("User not found.");
user.DisplayName = displayName;
_dbContext.Users.Update(user);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPut("{userId}/SetBiography")]
public async Task<IActionResult> SetBiography(string userId, [FromBody]string biography)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId);
if (user == null)
return NotFound("User not found.");
user.Biography = biography;
_dbContext.Users.Update(user);
await _dbContext.SaveChangesAsync();
return Ok();
}
}

View File

@ -0,0 +1,120 @@
using comissions.app.api.Models.SellerProfile;
using comissions.app.api.Models.SellerService;
using ArtPlatform.Database;
using comissions.app.api.Models.Discovery;
using comissions.app.database;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class DiscoveryController : Controller
{
private readonly ApplicationDbContext _dbContext;
public DiscoveryController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
[Route("Sellers")]
public async Task<IActionResult> GetSellers(string search="",int offset = 0, int pageSize = 10)
{
var sellers = await _dbContext.UserSellerProfiles
.Where(x=>x.User.DisplayName.Contains(search))
.Include(x=>x.User)
.Skip(offset).Take(pageSize).ToListAsync();
var result = sellers.Select(x=>x.ToDiscoveryModel()).ToList();
return Ok(result);
}
[HttpGet]
[Route("Sellers/Count")]
public async Task<IActionResult> GetSellersCount(string search="")
{
var result = await _dbContext.UserSellerProfiles
.Where(x=>x.User.DisplayName.Contains(search))
.Include(x=>x.User)
.CountAsync();
return Ok(result);
}
[HttpGet]
[Route("Sellers/{sellerId:int}/Services")]
public async Task<IActionResult> GetSellerServices(int sellerId, int offset = 0, int pageSize = 10)
{
var seller = await _dbContext.UserSellerProfiles
.Include(x=>x.User)
.FirstOrDefaultAsync(x=>x.Id==sellerId);
if(seller==null)
return NotFound("Seller not found.");
var sellerServices = await _dbContext.SellerServices
.Include(x=>x.Reviews)
.Where(x=>x.SellerProfileId==sellerId && !x.Archived)
.Skip(offset).Take(pageSize).ToListAsync();
var result = sellerServices.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpGet]
[Route("Sellers/{sellerId:int}/Services/Count")]
public async Task<IActionResult> GetSellerServicesCount(int sellerId)
{
var seller = await _dbContext.UserSellerProfiles
.Include(x=>x.User)
.FirstOrDefaultAsync(x=>x.Id==sellerId);
if(seller==null)
return NotFound("Seller not found.");
var sellerServices = await _dbContext.SellerServices
.Include(x=>x.Reviews)
.Where(x=>x.SellerProfileId==sellerId && !x.Archived)
.ToListAsync();
var result = sellerServices.Count;
return Ok(result);
}
[HttpGet]
[Route("Sellers/{sellerId:int}/Services/{serviceId:int}/Reviews")]
public async Task<IActionResult> GetSellerServiceReviews(int sellerId, int serviceId, int offset = 0, int pageSize = 10)
{
var seller = await _dbContext.UserSellerProfiles
.Include(x=>x.User)
.FirstOrDefaultAsync(x=>x.Id==sellerId);
if(seller==null)
return NotFound("Seller not found.");
var sellerService = await _dbContext.SellerServices
.Include(x=>x.Reviews).ThenInclude(x=>x.Reviewer)
.FirstOrDefaultAsync(x=>x.Id==serviceId);
if(sellerService==null)
return NotFound("Seller service not found.");
var result = sellerService.Reviews.Select(x=> new DiscoveryReviewModel()
{
Rating = x.Rating,
WriterDisplayName = x.Reviewer.DisplayName,
WriterId = x.ReviewerId,
}).ToList();
return Ok(result);
}
[HttpGet]
[Route("Sellers/{sellerId:int}/Services/{serviceId:int}/Reviews/Count")]
public async Task<IActionResult> GetSellerServiceReviewsCount(int sellerId, int serviceId)
{
var seller = await _dbContext.UserSellerProfiles
.Include(x=>x.User)
.FirstOrDefaultAsync(x=>x.Id==sellerId);
if(seller==null)
return NotFound("Seller not found.");
var sellerService = await _dbContext.SellerServices
.Include(x=>x.Reviews).ThenInclude(x=>x.Reviewer)
.FirstOrDefaultAsync(x=>x.Id==serviceId);
if(sellerService==null)
return NotFound("Seller service not found.");
var result = sellerService.Reviews.Count;
return Ok(result);
}
}

View File

@ -0,0 +1,463 @@
using comissions.app.api.Extensions;
using ArtPlatform.Database;
using ArtPlatform.Database.Entities;
using comissions.app.api.Models.Order;
using comissions.app.api.Services.Payment;
using comissions.app.api.Services.Storage;
using comissions.app.database;
using comissions.app.database.Entities;
using comissions.app.database.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Stripe;
using Stripe.Checkout;
namespace comissions.app.api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class OrderController : Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
private readonly IPaymentService _paymentService;
private readonly string _webHookSecret;
public OrderController(ApplicationDbContext dbContext, IConfiguration configuration, IStorageService storageService, IPaymentService paymentService)
{
_paymentService = paymentService;
_storageService = storageService;
_dbContext = dbContext;
_webHookSecret = configuration.GetValue<string>("Stripe:WebHookSecret");
}
[HttpPost("PaymentWebhook")]
public async Task<IActionResult> ProcessWebhookEvent()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
// If you are testing your webhook locally with the Stripe CLI you
// can find the endpoint's secret by running `stripe listen`
// Otherwise, find your endpoint's secret in your webhook settings
// in the Developer Dashboard
var stripeEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], _webHookSecret);
if (stripeEvent.Type == Events.CheckoutSessionExpired)
{
var session = stripeEvent.Data.Object as Session;
var connectedAccountId = stripeEvent.Account;
var orderId = session.LineItems.First().Price.Product.Name;
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Seller)
.Include(x=>x.Buyer)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==int.Parse(orderId));
if (order != null && order.Status == EnumOrderStatus.WaitingForPayment)
{
order.PaymentUrl = null;
}
}
else if (stripeEvent.Type == Events.CheckoutSessionCompleted)
{
var session = stripeEvent.Data.Object as Session;
var connectedAccountId = stripeEvent.Account;
var orderId = session.Metadata["/OrderId"];
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==int.Parse(orderId));
if (order != null && order.Seller.StripeAccountId==connectedAccountId && order.Status == EnumOrderStatus.WaitingForPayment)
{
if (order.Seller.PrepaymentRequired)
order.Status = EnumOrderStatus.InProgress;
else
order.Status = EnumOrderStatus.Completed;
}
}
return Ok();
}
[HttpGet]
[Route("/api/Orders")]
[Authorize("read:orders")]
public async Task<IActionResult> GetOrders(int offset = 0, int pageSize = 10, EnumOrderStatus? status = null)
{
var userId = User.GetUserId();
var orders = await _dbContext.SellerServiceOrders
.Where(x => x.BuyerId == userId && status==null ? true : status==x.Status)
.Skip(offset).Take(pageSize).ToListAsync();
var result = orders.Select(x => x.ToModel()).ToList();
return Ok(result);
}
[HttpGet]
[Route("/api/Orders/{orderId:int}")]
[Authorize("read:orders")]
public async Task<IActionResult> GetOrder(int orderId,int offset = 0, int pageSize = 10, EnumOrderStatus? status = null)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.FirstAsync(x => x.Id==orderId && x.BuyerId == userId && status == null ? true : status == x.Status);
var result = order.ToModel();
return Ok(result);
}
[HttpPost]
[Route("/api/Sellers/{sellerId:int}/Services/{serviceId:int}")]
[Authorize("write:orders")]
public async Task<IActionResult> CreateOrder(int sellerId, int serviceId)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles
.Include(x=>x.User)
.FirstOrDefaultAsync(x=>x.Id==sellerId);
if(seller==null)
return NotFound("Seller not found.");
if(seller.Suspended)
return NotFound("Seller is suspended.");
var service = await _dbContext.SellerServices
.Include(x=>x.Reviews)
.FirstOrDefaultAsync(x=>x.Id==serviceId);
if(service==null)
return NotFound("Service not found.");
if(service.Archived)
return BadRequest("Service is archived.");
if(_dbContext.SellerServiceOrders.Where(x=>x.BuyerId==userId && x.Status!=EnumOrderStatus.Completed && x.Status!=EnumOrderStatus.Cancelled).Count()>=3)
return BadRequest("You already have an order in progress. There is a limit of three at a time.");
var order = new SellerServiceOrder()
{
BuyerId = userId,
SellerId = seller.Id,
SellerServiceId = serviceId,
Status = EnumOrderStatus.PendingAcceptance,
CreatedDate = DateTime.UtcNow,
Price = service.Price,
SellerService = service,
Buyer = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId),
};
order = _dbContext.SellerServiceOrders.Add(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpDelete]
[Authorize("write:orders")]
[Route("/api/Orders/{orderId:int}")]
public async Task<IActionResult> CancelOrder(int orderId)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.BuyerId==userId);
if(order==null)
return NotFound("/Order not found.");
if(order.BuyerId!=userId)
return BadRequest("You are not the buyer of this order.");
if(order.Status==EnumOrderStatus.Completed)
return BadRequest("/Order is not in a cancellable state.");
order.Status = EnumOrderStatus.Cancelled;
order.EndDate = DateTime.UtcNow;
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:orders")]
[Route("/api/Orders/{orderId:int}/AcceptPrice")]
public async Task<IActionResult> AcceptPrice(int orderId)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.SellerService)
.Include(x=>x.Buyer)
.Include(x=>x.Seller)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.BuyerId==userId);
if(order==null)
return NotFound("/Order not found.");
if(order.Seller.UserId!=userId)
return BadRequest("You are not the seller of this order.");
if(order.Status==EnumOrderStatus.Completed)
return BadRequest("/Order is already complete.");
if(order.Status<EnumOrderStatus.DiscussingRequirements)
return BadRequest("/Order has not been started yet.");
if(string.IsNullOrEmpty(order.PaymentUrl)==false)
return BadRequest("/Order has price already been agreed on.");
if(order.Status==EnumOrderStatus.WaitingForPayment)
return BadRequest("/Order is waiting for payment.");
order.TermsAcceptedDate = DateTime.UtcNow;
if (order.Seller.PrepaymentRequired)
{
order.Status = EnumOrderStatus.WaitingForPayment;
var url = _paymentService.ChargeForService(order.Id, order.Seller.StripeAccountId, order.Price);
order.PaymentUrl = url;
}
else
{
order.Status = EnumOrderStatus.InProgress;
}
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:orders")]
[Route("/api/Orders/{orderId:int}/Payment")]
public async Task<IActionResult> Payment(int orderId)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.SellerService)
.Include(x=>x.Buyer)
.Include(x=>x.Seller)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.BuyerId==userId);
if(order==null)
return NotFound("/Order not found.");
if(order.Seller.UserId!=userId)
return BadRequest("You are not the seller of this order.");
if(order.Status==EnumOrderStatus.Completed)
return BadRequest("/Order is already complete.");
if(order.Status!=EnumOrderStatus.WaitingForPayment)
return BadRequest("/Order does not need to be paid for.");
if (order.PaymentUrl != null)
return Ok(order.PaymentUrl);
var url = _paymentService.ChargeForService(order.Id, order.Seller.StripeAccountId, order.Price);
order.PaymentUrl = url;
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
return Ok(order.PaymentUrl);
}
[HttpPut]
[Authorize("write:orders")]
[Route("/api/Orders/{orderId:int}/Accept")]
public async Task<IActionResult> Accept(int orderId)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.BuyerId==userId);
if(order==null)
return NotFound("/Order not found.");
if(order.Seller.UserId!=userId)
return BadRequest("You are not the seller of this order.");
if(order.Status==EnumOrderStatus.Completed)
return BadRequest("/Order is already complete.");
if(order.Status<EnumOrderStatus.InProgress)
return BadRequest("/Order has not been started yet.");
if(order.Status<EnumOrderStatus.PendingReview)
return BadRequest("/Order is in progress and not pending review.");
if(order.Status==EnumOrderStatus.WaitingForPayment)
return BadRequest("/Order is waiting for payment.");
if(order.Seller.PrepaymentRequired)
order.Status = EnumOrderStatus.Completed;
else
{
order.Status = EnumOrderStatus.WaitingForPayment;
var url = _paymentService.ChargeForService(order.Id, order.Seller.StripeAccountId, order.Price);
order.PaymentUrl = url;
}
order.TermsAcceptedDate = DateTime.UtcNow;
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpDelete]
[Authorize("write:orders")]
[Route("/api/Orders/{orderId:int}/Deny")]
public async Task<IActionResult> Deny(int orderId)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.BuyerId==userId);
if(order==null)
return NotFound("/Order not found.");
if(order.Seller.UserId!=userId)
return BadRequest("You are not the seller of this order.");
if(order.Status==EnumOrderStatus.Completed)
return BadRequest("/Order is already complete.");
if(order.Status<EnumOrderStatus.InProgress)
return BadRequest("/Order has not been started yet.");
if(order.Status<EnumOrderStatus.PendingReview)
return BadRequest("/Order is in progress and not pending review.");
order.Status = EnumOrderStatus.InProgress;
order.TermsAcceptedDate = DateTime.UtcNow;
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpPost]
[Authorize("write:orders")]
[Route("/api/Orders/{orderId:int}/Review")]
public async Task<IActionResult> Review(int orderId, [FromBody] SellerServiceOrderReviewModel model)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Reviews)
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.BuyerId==userId);
if(order==null)
return NotFound("/Order not found.");
if(order.BuyerId!=userId)
return BadRequest("You are not the buyer of this order.");
if(order.Status!=EnumOrderStatus.Completed)
return BadRequest("/Order is not complete.");
if(order.Reviews.Any(x=>x.SellerServiceOrderId==orderId))
return BadRequest("/Order has already been reviewed.");
var review = new SellerServiceOrderReview()
{
SellerServiceOrderId = orderId,
SellerServiceId = order.SellerServiceId,
Rating = model.Rating,
Review = model.Review,
ReviewDate = DateTime.UtcNow,
ReviewerId = userId,
Reviewer = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId),
};
await _dbContext.SellerServiceOrderReviews.AddAsync(review);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
[Authorize("read:orders")]
[Route("/api/Orders/{orderId:int}/Messages")]
public async Task<IActionResult> GetMessages(int orderId, int offset = 0, int pageSize = 10)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Seller)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.BuyerId==userId);
if(order==null)
return NotFound("/Order not found.");
if(order.BuyerId!=userId && order.Seller.UserId!=userId)
return BadRequest("You are not the buyer or seller of this order.");
var messages = _dbContext.SellerServiceOrderMessages
.Include(x=>x.Sender)
.Include(x=>x.Attachments)
.OrderBy(x=>x.SentAt)
.Where(x=>x.SellerServiceOrderId==orderId)
.Skip(offset).Take(pageSize).ToList();
var result = messages.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpPost]
[Authorize("write:orders")]
[Route("/api/Orders/{orderId:int}/Message")]
public async Task<IActionResult> Message(int orderId, [FromBody] SellerServiceOrderMessageModel model)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Messages)
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.BuyerId==userId);
if(order==null)
return NotFound("/Order not found.");
if(order.Status==EnumOrderStatus.Completed || order.Status==EnumOrderStatus.Cancelled)
return BadRequest("/Order is already complete.");
if(order.BuyerId!=userId && order.Seller.UserId!=userId)
return BadRequest("You are not the buyer or seller of this order.");
if(order.Status<EnumOrderStatus.Waitlist)
return BadRequest("/Order is not accepted.");
var message = new SellerServiceOrderMessage()
{
SellerServiceOrderId = orderId,
Message = model.Message,
SentAt = DateTime.UtcNow,
SenderId = userId,
Sender = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId),
};
var dbMessage = _dbContext.SellerServiceOrderMessages.Add(message).Entity;
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPost]
[Authorize("write:orders")]
[Route("/api/Orders/{orderId:int}/Message/{messageId:int}/Attachment")]
public async Task<IActionResult> MessageAttachment(int orderId, int messageId,IFormFile file)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Messages)
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId);
if(order==null)
return NotFound("/Order not found.");
if(order.BuyerId!=userId && order.Seller.UserId!=userId)
return BadRequest("You are not the buyer or seller of this order.");
if(order.Status==EnumOrderStatus.Completed || order.Status==EnumOrderStatus.Cancelled)
return BadRequest("/Order is already complete.");
if(order.Status<EnumOrderStatus.Waitlist)
return BadRequest("/Order is not accepted.");
var message = _dbContext.SellerServiceOrderMessages.First(x=>x.Id==messageId && x.SellerServiceOrderId==orderId);
if(message==null)
return BadRequest("Message does not exist or does not belong to this order.");
var url = await _storageService.UploadImageAsync(file, Guid.NewGuid().ToString());
var attachment = new SellerServiceOrderMessageAttachment()
{
SellerServiceOrderMessageId = message.Id,
FileReference = url
};
_dbContext.SellerServiceOrderMessageAttachments.Add(attachment);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
[Authorize("read:orders")]
[Route("/api/Orders/{orderId:int}/Message/{messageId:int}/Attachment")]
public async Task<IActionResult> MessageAttachments(int orderId, int messageId)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Messages)
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId);
if(order==null)
return NotFound("/Order not found.");
if(order.BuyerId!=userId && order.Seller.UserId!=userId)
return BadRequest("You are not the buyer or seller of this order.");
if(order.Status==EnumOrderStatus.Completed || order.Status==EnumOrderStatus.Cancelled)
return BadRequest("/Order is already complete.");
if(order.Status<EnumOrderStatus.Waitlist)
return BadRequest("/Order is not accepted.");
var message = _dbContext.SellerServiceOrderMessages.Include(x=>x.Attachments)
.First(x=>x.Id==messageId && x.SellerServiceOrderId==orderId);
if(message==null)
return BadRequest("Message does not exist or does not belong to this order.");
var content = await _storageService.DownloadImageAsync(message.Attachments.First().FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
}

View File

@ -0,0 +1,346 @@
using comissions.app.api.Extensions;
using comissions.app.api.Models.Order;
using ArtPlatform.Database;
using ArtPlatform.Database.Entities;
using comissions.app.api.Services.Storage;
using comissions.app.database;
using comissions.app.database.Entities;
using comissions.app.database.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class SellerOrderController : Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
public SellerOrderController(IStorageService storageService, ApplicationDbContext dbContext)
{
_storageService = storageService;
_dbContext = dbContext;
}
[HttpGet]
[Route("/api/SellerOrders")]
[Authorize("read:seller-orders")]
public async Task<IActionResult> GetOrders(int offset = 0, int pageSize = 10, EnumOrderStatus? status = null)
{
var userId = User.GetUserId();
var orders = await _dbContext.SellerServiceOrders
.Include(x=>x.Seller)
.Where(x => x.Seller.UserId == userId && status==null ? true : status==x.Status)
.Skip(offset).Take(pageSize).ToListAsync();
var result = orders.Select(x => x.ToModel()).ToList();
return Ok(result);
}
[HttpGet]
[Route("/api/SellerOrders/{orderId:int}")]
[Authorize("read:seller-orders")]
public async Task<IActionResult> GetOrder(int orderId, int offset = 0, int pageSize = 10, EnumOrderStatus? status = null)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x => x.Seller)
.FirstAsync(x => x.Id==orderId && x.Seller.UserId == userId && status == null ? true : status == x.Status);
var result = order.ToModel();
return Ok(result);
}
[HttpDelete]
[Authorize("write:seller-orders")]
[Route("/api/SellerOrders/{orderId:int}/Cancel")]
public async Task<IActionResult> CancelOrder(int orderId)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(x=>x.UserId==userId);
if(seller==null)
return NotFound("User it not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.Seller.UserId==userId);
if(order==null)
return NotFound("Order not found.");
if(order.Status==EnumOrderStatus.Completed || order.Status== EnumOrderStatus.Cancelled)
return BadRequest("Order is already complete.");
if(order.BuyerId!=userId)
return BadRequest("You are not the buyer of this order.");
if(order.Status!=EnumOrderStatus.Completed && order.Status!= EnumOrderStatus.Cancelled)
return BadRequest("Order is not in a cancellable state.");
order.Status = EnumOrderStatus.Cancelled;
order.EndDate = DateTime.UtcNow;
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:seller-orders")]
[Route("/api/SellerOrders/{orderId:int}/Accept")]
public async Task<IActionResult> AcceptOrder(int orderId)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(x=>x.UserId==userId);
if(seller==null)
return NotFound("User it not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.Seller.UserId==userId);
if(order==null)
return NotFound("Order not found.");
if(order.Status==EnumOrderStatus.Completed || order.Status== EnumOrderStatus.Cancelled)
return BadRequest("Order is already complete.");
if(order.BuyerId!=userId)
return BadRequest("You are not the buyer of this order.");
if(order.Status!=EnumOrderStatus.PendingAcceptance)
return BadRequest("Order has already been accepted.");
order.Status = EnumOrderStatus.Waitlist;
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:seller-orders")]
[Route("/api/SellerOrders/{orderId:int}/Start")]
public async Task<IActionResult> StartOrder(int orderId)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(x=>x.UserId==userId);
if(seller==null)
return NotFound("User it not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.Seller.UserId==userId);
if(order==null)
return NotFound("Order not found.");
if(order.Status==EnumOrderStatus.Completed || order.Status== EnumOrderStatus.Cancelled)
return BadRequest("Order is already complete.");
if(order.BuyerId!=userId)
return BadRequest("You are not the buyer of this order.");
if(order.Status!=EnumOrderStatus.Waitlist)
return BadRequest("Order has already been started.");
order.Status = EnumOrderStatus.DiscussingRequirements;
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:seller-orders")]
[Route("/api/SellerOrders/{orderId:int}/AdjustPrice")]
public async Task<IActionResult> AdjustPrice(int orderId,[FromQuery]double price)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(x=>x.UserId==userId);
if(seller==null)
return NotFound("User it not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.Seller.UserId==userId);
if(order==null)
return NotFound("Order not found.");
if(order.Seller.UserId!=userId)
return BadRequest("You are not the seller of this order.");
if(order.Status==EnumOrderStatus.Completed || order.Status== EnumOrderStatus.Cancelled)
return BadRequest("Order is already complete.");
if(order.Status>EnumOrderStatus.DiscussingRequirements)
return BadRequest("Order requirements and price have already been confirmed.");
if(order.Status<EnumOrderStatus.DiscussingRequirements)
return BadRequest("Order has not been started.");
order.Price = price;
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:seller-orders")]
[Route("/api/SellerOrders/{orderId:int}/CompleteRevision")]
public async Task<IActionResult> CompleteRevision(int orderId)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.Seller.UserId==userId);
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(x=>x.UserId==userId);
if(seller==null)
return NotFound("User it not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
if(order==null)
return NotFound("Order not found.");
if(order.Seller.UserId!=userId)
return BadRequest("You are not the seller of this order.");
if(order.Status==EnumOrderStatus.Completed || order.Status== EnumOrderStatus.Cancelled)
return BadRequest("Order is already complete.");
if(order.Status<EnumOrderStatus.InProgress)
return BadRequest("Order has not been started.");
if(order.Status>EnumOrderStatus.InProgress)
return BadRequest("Order is pending review already.");
order.Status = EnumOrderStatus.PendingReview;
order = _dbContext.SellerServiceOrders.Update(order).Entity;
await _dbContext.SaveChangesAsync();
var result = order.ToModel();
return Ok(result);
}
[HttpGet]
[Authorize("read:orders")]
[Route("/api/SellerOrders/{orderId:int}/Messages")]
public async Task<IActionResult> GetMessages(int orderId, int offset = 0, int pageSize = 10)
{
var userId = User.GetUserId();
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Seller)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.Seller.UserId==userId);
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(x=>x.UserId==userId);
if(seller==null)
return NotFound("User it not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
if(order==null)
return NotFound("Order not found.");
if(order.BuyerId!=userId && order.Seller.UserId!=userId)
return BadRequest("You are not the buyer or seller of this order.");
var messages = _dbContext.SellerServiceOrderMessages
.Include(x=>x.Sender)
.Include(x=>x.Attachments)
.OrderBy(x=>x.SentAt)
.Where(x=>x.SellerServiceOrderId==orderId)
.Skip(offset).Take(pageSize).ToList();
var result = messages.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpPost]
[Authorize("write:orders")]
[Route("/api/SellerOrders/{orderId:int}/Message")]
public async Task<IActionResult> Message(int orderId, [FromBody] SellerServiceOrderMessageModel model)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(x=>x.UserId==userId);
if(seller==null)
return NotFound("User it not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Messages)
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.Seller.UserId==userId);
if(order==null)
return NotFound("Order not found.");
if(order.Status==EnumOrderStatus.Completed || order.Status==EnumOrderStatus.Cancelled)
return BadRequest("Order is already complete.");
if(order.BuyerId!=userId && order.Seller.UserId!=userId)
return BadRequest("You are not the buyer or seller of this order.");
if(order.Status<EnumOrderStatus.Waitlist)
return BadRequest("Order is not accepted.");
var message = new SellerServiceOrderMessage()
{
SellerServiceOrderId = orderId,
Message = model.Message,
SentAt = DateTime.UtcNow,
SenderId = userId,
Sender = await _dbContext.Users.FirstOrDefaultAsync(x=>x.Id==userId),
};
var dbMessage = _dbContext.SellerServiceOrderMessages.Add(message).Entity;
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPost]
[Authorize("write:orders")]
[Route("/api/SellerOrders/{orderId:int}/Message/{messageId:int}/Attachment")]
public async Task<IActionResult> MessageAttachment(int orderId, int messageId,IFormFile file)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(x=>x.UserId==userId);
if(seller==null)
return NotFound("User it not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Messages)
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId && x.Seller.UserId==userId);
if(order==null)
return NotFound("Order not found.");
if(order.BuyerId!=userId && order.Seller.UserId!=userId)
return BadRequest("You are not the buyer or seller of this order.");
if(order.Status==EnumOrderStatus.Completed || order.Status==EnumOrderStatus.Cancelled)
return BadRequest("Order is already complete.");
if(order.Status<EnumOrderStatus.Waitlist)
return BadRequest("Order is not accepted.");
var message = _dbContext.SellerServiceOrderMessages.First(x=>x.Id==messageId && x.SellerServiceOrderId==orderId);
if(message==null)
return BadRequest("Message does not exist or does not belong to this order.");
var url = await _storageService.UploadImageAsync(file, Guid.NewGuid().ToString());
var attachment = new SellerServiceOrderMessageAttachment()
{
SellerServiceOrderMessageId = message.Id,
FileReference = url
};
_dbContext.SellerServiceOrderMessageAttachments.Add(attachment);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
[Authorize("read:orders")]
[Route("/api/SellerOrders/{orderId:int}/Message/{messageId:int}/Attachment")]
public async Task<IActionResult> MessageAttachments(int orderId, int messageId)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(x=>x.UserId==userId);
if(seller==null)
return NotFound("User it not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var order = await _dbContext.SellerServiceOrders
.Include(x=>x.Messages)
.Include(x=>x.Seller)
.Include(x=>x.SellerService)
.FirstOrDefaultAsync(x=>x.Id==orderId);
if(order==null)
return NotFound("Order not found.");
if(order.BuyerId!=userId && order.Seller.UserId!=userId)
return BadRequest("You are not the buyer or seller of this order.");
if(order.Status==EnumOrderStatus.Completed || order.Status==EnumOrderStatus.Cancelled)
return BadRequest("Order is already complete.");
if(order.Status<EnumOrderStatus.Waitlist)
return BadRequest("Order is not accepted.");
var message = _dbContext.SellerServiceOrderMessages.Include(x=>x.Attachments)
.First(x=>x.Id==messageId && x.SellerServiceOrderId==orderId);
if(message==null)
return BadRequest("Message does not exist or does not belong to this order.");
var attachment = message.Attachments.FirstOrDefault();
if(attachment==null)
return BadRequest("Message does not have an attachment.");
var content = await _storageService.DownloadImageAsync(message.Attachments.First().FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
}

View File

@ -0,0 +1,246 @@
using comissions.app.api.Extensions;
using comissions.app.api.Models.PortfolioModel;
using ArtPlatform.Database;
using ArtPlatform.Database.Entities;
using comissions.app.api.Models.SellerProfile;
using comissions.app.api.Services.Payment;
using comissions.app.api.Services.Storage;
using comissions.app.database;
using comissions.app.database.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class SellerProfileController : Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
private readonly IPaymentService _paymentService;
public SellerProfileController(ApplicationDbContext dbContext, IPaymentService paymentService, IStorageService storageService)
{
_paymentService = paymentService;
_storageService = storageService;
_dbContext = dbContext;
}
[HttpGet]
[Authorize("read:seller-profile")]
public async Task<IActionResult> GetSellerProfile()
{
var userId = User.GetUserId();
var sellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if(sellerProfile==null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
var result = sellerProfile.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:seller-profile")]
public async Task<IActionResult> UpdateSellerProfile(SellerProfileModel model)
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
var updatedSellerProfile = model.ToModel(existingSellerProfile);
updatedSellerProfile = _dbContext.UserSellerProfiles.Update(updatedSellerProfile).Entity;
await _dbContext.SaveChangesAsync();
var result = updatedSellerProfile.ToModel();
return Ok(result);
}
[HttpPost]
[Authorize("write:seller-profile")]
public async Task<IActionResult> RequestSellerProfile(SellerProfileModel model)
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile != null)
{
return Unauthorized("Account is already a seller.");
}
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId);
if(sellerProfileRequest!=null)
return BadRequest("Account has already requested to be a seller.");
sellerProfileRequest = new SellerProfileRequest()
{
Accepted = false,
RequestDate = DateTime.UtcNow,
UserId = userId
};
_dbContext.SellerProfileRequests.Add(sellerProfileRequest);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
[Authorize("read:seller-profile")]
[Route("{sellerServiceId:int}/Portfolio/{portfolioId:int}")]
public async Task<IActionResult> GetPortfolio(int sellerServiceId, int portfolioId)
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
var portfolio = await _dbContext.SellerProfilePortfolioPieces
.FirstAsync(x => x.SellerProfileId == existingSellerProfile.Id && x.Id==portfolioId);
var content = await _storageService.DownloadImageAsync(portfolio.FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
[HttpGet]
[Route("Portfolio")]
[Authorize("read:seller-profile")]
public async Task<IActionResult> GetPortfolio()
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
var portfolio = await _dbContext.SellerProfilePortfolioPieces.Where(x=>x.SellerProfileId==existingSellerProfile.Id).ToListAsync();
var result = portfolio.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpPost]
[Route("Portfolio")]
[Authorize("write:seller-profile")]
public async Task<IActionResult> AddPortfolio(IFormFile file)
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
var url = await _storageService.UploadImageAsync(file, Guid.NewGuid().ToString());
var portfolio = new SellerProfilePortfolioPiece()
{
SellerProfileId = existingSellerProfile.Id,
FileReference = url
};
portfolio.SellerProfileId = existingSellerProfile.Id;
_dbContext.SellerProfilePortfolioPieces.Add(portfolio);
await _dbContext.SaveChangesAsync();
var result = portfolio.ToModel();
return Ok(result);
}
[HttpDelete]
[Authorize("write:seller-profile")]
[Route("Portfolio/{portfolioId:int}")]
public async Task<IActionResult> DeletePortfolio(int portfolioId)
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
var portfolio = await _dbContext.SellerProfilePortfolioPieces.FirstOrDefaultAsync(x=>x.Id==portfolioId);
if(portfolio==null)
return NotFound("Portfolio piece not found.");
if(portfolio.SellerProfileId!=existingSellerProfile.Id)
return BadRequest("Portfolio piece does not belong to this seller.");
_dbContext.SellerProfilePortfolioPieces.Remove(portfolio);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpPost]
[Authorize("write:seller-profile")]
[Route("Payment")]
public async Task<IActionResult> CreatePaymentAccount()
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
if(existingSellerProfile.StripeAccountId!=null)
return BadRequest("Account already have a payment account.");
var accountId = _paymentService.CreateSellerAccount();
existingSellerProfile.StripeAccountId = accountId;
existingSellerProfile = _dbContext.UserSellerProfiles.Update(existingSellerProfile).Entity;
await _dbContext.SaveChangesAsync();
var result = existingSellerProfile.ToModel();
return Ok(result);
}
[HttpGet]
[Authorize("write:seller-profile")]
[Route("Payment")]
public async Task<IActionResult> GetPaymentAccount()
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
if(existingSellerProfile.StripeAccountId==null)
return BadRequest("Account does not have a payment account.");
var result = _paymentService.CreateSellerAccountOnboardingUrl(existingSellerProfile.StripeAccountId);
return Ok(result);
}
}

View File

@ -0,0 +1,261 @@
using comissions.app.api.Extensions;
using comissions.app.api.Models.PortfolioModel;
using ArtPlatform.Database;
using ArtPlatform.Database.Entities;
using comissions.app.api.Models.SellerService;
using comissions.app.api.Services.Payment;
using comissions.app.api.Services.Storage;
using comissions.app.database;
using comissions.app.database.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class SellerServiceController : Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
private readonly IPaymentService _paymentService;
public SellerServiceController(ApplicationDbContext dbContext, IPaymentService paymentService, IStorageService storageService)
{
_paymentService = paymentService;
_storageService = storageService;
_dbContext = dbContext;
}
[HttpGet]
[Authorize("read:seller-service")]
public async Task<IActionResult> GetSellerServices(int offset=0, int pageSize=10)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if(seller==null)
return BadRequest("Account is not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var sellerServices = await _dbContext.SellerServices.Where(x=>x.Archived==false).Include(x=>x.Reviews)
.Skip(offset).Take(pageSize).ToListAsync();
var result = sellerServices.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpGet]
[Route("Count")]
[Authorize("read:seller-service")]
public async Task<IActionResult> GetSellerServicesCount()
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if(seller==null)
return BadRequest("Account is not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var sellerServices = await _dbContext.SellerServices.Where(x=>x.Archived==false).Include(x => x.Reviews).ToListAsync();
var result = sellerServices.Count;
return Ok(result);
}
[HttpPost]
[Authorize("write:seller-service")]
public async Task<IActionResult> CreateSellerService([FromBody] SellerServiceCreateModel model)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if(seller==null)
return BadRequest("Account is not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
if(seller.StripeAccountId==null)
return BadRequest("Account does not have a payment account.");
if (_paymentService.SellerAccountIsOnboarded(seller.StripeAccountId) == false)
return BadRequest("Account has not finished onboarding.");
var sellerService = new SellerService()
{
Name = model.Name,
Description = model.Description,
Price = model.Price,
SellerProfileId = seller.Id
};
sellerService = _dbContext.SellerServices.Add(sellerService).Entity;
await _dbContext.SaveChangesAsync();
var result = sellerService.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:seller-service")]
[Route("{sellerServiceId:int}")]
public async Task<IActionResult> UpdateSellerService([FromBody] SellerServiceUpdateModel model, int sellerServiceId)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if(seller==null)
return BadRequest("Account is not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var sellerService = await _dbContext.SellerServices.FirstOrDefaultAsync(sellerService=>sellerService.Id==sellerServiceId);
if(sellerService==null)
return NotFound("Seller service not found.");
sellerService.Name = model.Name;
sellerService.Description = model.Description;
sellerService.Price = model.Price;
sellerService = _dbContext.SellerServices.Update(sellerService).Entity;
await _dbContext.SaveChangesAsync();
var result = sellerService.ToModel();
return Ok(result);
}
[HttpDelete]
[Authorize("write:seller-service")]
[Route("{sellerServiceId:int}")]
public async Task<IActionResult> DeleteSellerService(int sellerServiceId)
{
var userId = User.GetUserId();
var seller = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if(seller==null)
return BadRequest("Account is not a seller.");
if(seller.Suspended)
return BadRequest("Seller is suspended.");
var sellerService = await _dbContext.SellerServices.FirstOrDefaultAsync(sellerService=>sellerService.Id==sellerServiceId);
if(sellerService==null)
return NotFound("Seller service not found.");
sellerService.Archived = true;
_dbContext.SellerServices.Update(sellerService);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
[Route("{sellerServiceId:int}/Portfolio/")]
public async Task<IActionResult> GetPortfolio(int sellerServiceId)
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
var portfolio = await _dbContext.SellerProfilePortfolioPieces.Where(x=>x.SellerProfileId==existingSellerProfile.Id && x.SellerServiceId==sellerServiceId).ToListAsync();
var result = portfolio.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpGet]
[Authorize("read:seller-service")]
[Route("{sellerServiceId:int}/Portfolio/{portfolioId:int}")]
public async Task<IActionResult> GetPortfolio(int sellerServiceId, int portfolioId)
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
var portfolio = await _dbContext.SellerProfilePortfolioPieces
.FirstAsync(x => x.SellerProfileId == existingSellerProfile.Id
&& x.SellerServiceId == sellerServiceId && x.Id==portfolioId);
var content = await _storageService.DownloadImageAsync(portfolio.FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
[HttpPost]
[Authorize("write:seller-service")]
[Route("{sellerServiceId:int}/Portfolio")]
public async Task<IActionResult> AddPortfolio(IFormFile file, int sellerServiceId)
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
var url = await _storageService.UploadImageAsync(file, Guid.NewGuid().ToString());
var portfolio = new SellerProfilePortfolioPiece()
{
SellerProfileId = existingSellerProfile.Id,
FileReference = url,
SellerServiceId = sellerServiceId
};
portfolio.SellerProfileId = existingSellerProfile.Id;
_dbContext.SellerProfilePortfolioPieces.Add(portfolio);
await _dbContext.SaveChangesAsync();
var result = portfolio.ToModel();
return Ok(result);
}
[HttpDelete]
[Authorize("write:seller-service")]
[Route("{sellerServiceId:int}/Portfolio/{portfolioId:int}")]
public async Task<IActionResult> DeletePortfolio(int portfolioId)
{
var userId = User.GetUserId();
var existingSellerProfile = await _dbContext.UserSellerProfiles.FirstOrDefaultAsync(sellerProfile=>sellerProfile.UserId==userId);
if (existingSellerProfile == null)
{
var sellerProfileRequest = await _dbContext.SellerProfileRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(sellerProfileRequest!=null)
return BadRequest("Account has requested to be a seller and not been approved yet.");
return Unauthorized("Account is not a seller.");
}
if(existingSellerProfile.Suspended)
return BadRequest("Seller is suspended.");
var portfolio = await _dbContext.SellerProfilePortfolioPieces.FirstOrDefaultAsync(x=>x.Id==portfolioId);
if(portfolio==null)
return NotFound("Portfolio piece not found.");
if(portfolio.SellerProfileId!=existingSellerProfile.Id)
return BadRequest("Portfolio piece does not belong to this seller.");
_dbContext.SellerProfilePortfolioPieces.Remove(portfolio);
await _dbContext.SaveChangesAsync();
return Ok();
}
}

View File

@ -0,0 +1,45 @@
using System.Security.Claims;
using comissions.app.api.Extensions;
using ArtPlatform.Database;
using comissions.app.api.Models.User;
using comissions.app.database;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class UserController : Controller
{
private readonly ApplicationDbContext _dbContext;
public UserController(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[Authorize("read:user")]
[HttpGet]
public async Task<IActionResult> GetUser()
{
var userId = User.GetUserId();
var user = await _dbContext.Users.FirstAsync(user=>user.Id==userId);
var result = user.ToModel();
return Ok(result);
}
[Authorize("write:user")]
[HttpPut]
public async Task<IActionResult> UpdateUser(UserInfoUpdateModel model)
{
var userId = User.GetUserId();
var existingUser = await _dbContext.Users.FirstAsync(user=>user.Id==userId);
var updatedUser = model.ToEntity(existingUser);
updatedUser = _dbContext.Users.Update(updatedUser).Entity;
await _dbContext.SaveChangesAsync();
var result = updatedUser.ToModel();
return Ok(result);
}
}

View File

@ -0,0 +1,24 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["/src/ArtPlatform.API/ArtPlatform.API.csproj", "ArtPlatform.API/"]
COPY ["/src/ArtPlatform.Database/ArtPlatform.Database.csproj", "ArtPlatform.Database/"]
RUN dotnet restore "ArtPlatform.API/ArtPlatform.API.csproj"
COPY . .
WORKDIR "/src/ArtPlatform.API"
RUN dotnet build "ArtPlatform.API.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "ArtPlatform.API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ArtPlatform.API.dll"]

View File

@ -0,0 +1,11 @@
using System.Security.Claims;
namespace comissions.app.api.Extensions;
public static class UserExtension
{
public static string GetUserId(this ClaimsPrincipal user)
{
return user.Claims.First(claim => claim.Type == ClaimTypes.NameIdentifier).Value;
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authorization;
namespace comissions.app.api.Middleware.Authentication;
public class HasScopeHandler : AuthorizationHandler<HasScopeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement)
{
// If user does not have the scope claim, get out of here
if (!context.User.HasClaim(c => c.Type == "scope" && c.Issuer == requirement.Issuer))
return Task.CompletedTask;
// Split the scopes string into an array
var scopes = context.User.FindFirst(c => c.Type == "scope" && c.Issuer == requirement.Issuer).Value.Split(' ');
// Succeed if the scope array contains the required scope
if (scopes.Any(s => s == requirement.Scope))
context.Succeed(requirement);
return Task.CompletedTask;
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Authorization;
namespace comissions.app.api.Middleware.Authentication;
public class HasScopeRequirement : IAuthorizationRequirement
{
public string Issuer { get; }
public string Scope { get; }
public HasScopeRequirement(string scope, string issuer)
{
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
}
}

View File

@ -0,0 +1,117 @@
using System.Security.Claims;
using ArtPlatform.Database;
using ArtPlatform.Database.Entities;
using comissions.app.api.Services.Payment;
using comissions.app.database;
using comissions.app.database.Entities;
using Microsoft.EntityFrameworkCore;
namespace comissions.app.api.Middleware;
public class UserMiddleware
{
private readonly RequestDelegate _next;
public UserMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, ApplicationDbContext dbContext, IPaymentService paymentService)
{
if (context.User.Identity.IsAuthenticated)
{
var userId = context.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
var user = await dbContext.Users.Include(x=>x.UserSellerProfile).FirstOrDefaultAsync(x=>x.Id==userId);
if (user == null)
{
user = new User
{
Id = userId,
DisplayName = context.User.Claims.FirstOrDefault(x=>x.Type==ClaimTypes.Name)?.Value ?? "Anonymous",
Biography = string.Empty,
Email = context.User.Claims.FirstOrDefault(x=>x.Type==ClaimTypes.Email)?.Value ?? string.Empty,
};
dbContext.Users.Add(user);
await dbContext.SaveChangesAsync();
}
else
{
user.Email= context.User.Claims.FirstOrDefault(x=>x.Type==ClaimTypes.Email)?.Value ?? string.Empty;
dbContext.Users.Update(user);
await dbContext.SaveChangesAsync();
}
if (user.Suspended)
{
if (user.UnsuspendDate < DateTime.UtcNow)
{
user.Suspended = false;
user.SuspendedDate = null;
user.UnsuspendDate = null;
user.SuspendedReason = null;
user.SuspendAdminId = null;
dbContext.Users.Update(user);
await dbContext.SaveChangesAsync();
}
else
{
var suspendDate = user.SuspendedDate.Value.ToString("MM/dd/yyyy");
var unsuspendDate = user.UnsuspendDate.Value.ToString("MM/dd/yyyy");
await context.Response.WriteAsync($"Suspended on {suspendDate} until {unsuspendDate} for {user.SuspendedReason} by {user.SuspendAdminId}.");
context.Response.StatusCode = StatusCodes.Status403Forbidden;
return;
}
}
if (user.Banned)
{
if (user.UnsuspendDate < DateTime.UtcNow)
{
user.Banned = false;
user.BannedDate = null;
user.BannedDate = null;
user.BannedReason = null;
user.BanAdminId = null;
dbContext.Users.Update(user);
await dbContext.SaveChangesAsync();
}
else
{
var suspendDate = user.BannedDate.Value.ToString("MM/dd/yyyy");
var unsuspendDate = user.UnbanDate.Value.ToString("MM/dd/yyyy");
await context.Response.WriteAsync($"Banned on {suspendDate} until {unsuspendDate} for {user.BannedReason} by {user.BanAdminId}.");
context.Response.StatusCode = StatusCodes.Status403Forbidden;
return;
}
}
if (user.UserSellerProfile != null && user.UserSellerProfile.Suspended)
{
if (user.UserSellerProfile.UnsuspendDate < DateTime.UtcNow)
{
user.UserSellerProfile.Suspended = false;
user.UserSellerProfile.SuspendedDate = null;
user.UserSellerProfile.UnsuspendDate = null;
user.UserSellerProfile.SuspendedReason = null;
user.UserSellerProfile.SuspendAdminId = null;
dbContext.Users.Update(user);
await dbContext.SaveChangesAsync();
}
else
{
var suspendDate = user.UserSellerProfile.SuspendedDate.Value.ToString("MM/dd/yyyy");
var unsuspendDate = user.UserSellerProfile.UnsuspendDate.Value.ToString("MM/dd/yyyy");
await context.Response.WriteAsync($"Banned on {suspendDate} until {unsuspendDate} for {user.UserSellerProfile.SuspendedReason} by {user.UserSellerProfile.SuspendAdminId}.");
context.Response.StatusCode = StatusCodes.Status403Forbidden;
return;
}
}
}
await _next(context);
}
}

View File

@ -0,0 +1,8 @@
namespace comissions.app.api.Models.Discovery;
public class DiscoveryReviewModel
{
public string WriterDisplayName { get; set; }
public string WriterId { get; set; }
public double Rating { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace comissions.app.api.Models.Discovery;
public class DiscoverySellerModel
{
public int Id { get; set; }
public List<string> SocialMediaLinks { get; set; }
public string Biography { get; set; }
public bool PrepaymentRequired { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace comissions.app.api.Models.Order;
public class MessageModel
{
public int Id { get; set; }
public string SenderId { get; set; }
public string SenderDisplayName { get; set; }
public string Message { get; set; }
public int[] Attachments { get; set; }
}

View File

@ -0,0 +1,20 @@
using ArtPlatform.Database.Entities;
using comissions.app.database.Entities;
namespace comissions.app.api.Models.Order;
public static class MessageModelExtensions
{
public static MessageModel ToModel(this SellerServiceOrderMessage sellerProfile)
{
return new MessageModel()
{
Id = sellerProfile.Id,
SenderId = sellerProfile.SenderId,
SenderDisplayName = sellerProfile.Sender.DisplayName,
Message = sellerProfile.Message,
Attachments = sellerProfile.Attachments.Select(x=>x.Id).ToArray()
};
}
}

View File

@ -0,0 +1,16 @@
using comissions.app.database.Enums;
namespace comissions.app.api.Models.Order;
public class OrderModel
{
public int Id { get; set; }
public string BuyerId { get; set; }
public int SellerServiceId { get; set; }
public int SellerId { get; set; }
public EnumOrderStatus Status { get; set; }
public string StatusLabel => Status.ToString();
public double Price { get; set; }
public string? PaymentUrl { get; set; }
}

View File

@ -0,0 +1,22 @@
using ArtPlatform.Database.Entities;
using comissions.app.database.Entities;
namespace comissions.app.api.Models.Order;
public static class OrderModelExtensions
{
public static OrderModel ToModel(this SellerServiceOrder sellerProfile)
{
return new OrderModel()
{
Id = sellerProfile.Id,
BuyerId = sellerProfile.BuyerId,
SellerServiceId = sellerProfile.SellerServiceId,
SellerId = sellerProfile.SellerId,
Status = sellerProfile.Status,
Price = sellerProfile.Price,
PaymentUrl = sellerProfile.PaymentUrl
};
}
}

View File

@ -0,0 +1,6 @@
namespace ArtPlatform.Database.Entities;
public class SellerServiceOrderMessageModel
{
public string Message { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace comissions.app.api.Models.Order;
public class SellerServiceOrderReviewModel
{
public int Rating { get; set; }
public string? Review { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace comissions.app.api.Models.PortfolioModel;
public class PortfolioModel
{
public int Id { get; set; }
public int? SellerServiceId { get; set; }
}

View File

@ -0,0 +1,16 @@
using ArtPlatform.Database.Entities;
using comissions.app.database.Entities;
namespace comissions.app.api.Models.PortfolioModel;
public static class PortfolioModelExtensions
{
public static PortfolioModel ToModel(this SellerProfilePortfolioPiece sellerProfileRequest)
{
return new PortfolioModel()
{
Id = sellerProfileRequest.Id,
SellerServiceId = sellerProfileRequest.SellerServiceId
};
}
}

View File

@ -0,0 +1,8 @@
namespace comissions.app.api.Models.SellerProfile;
public class SellerProfileModel
{
public List<string> SocialMediaLinks { get; set; }
public string Biography { get; set; }
public bool PrepaymentRequired { get; set; }
}

View File

@ -0,0 +1,35 @@
using ArtPlatform.Database.Entities;
using comissions.app.api.Models.Discovery;
using comissions.app.database.Entities;
namespace comissions.app.api.Models.SellerProfile;
public static class SellerProfileModelExtensions
{
public static SellerProfileModel ToModel(this UserSellerProfile sellerProfile)
{
return new SellerProfileModel()
{
SocialMediaLinks = sellerProfile.SocialMediaLinks,
Biography = sellerProfile.Biography,
PrepaymentRequired = sellerProfile.PrepaymentRequired
};
}
public static DiscoverySellerModel ToDiscoveryModel(this UserSellerProfile sellerProfile)
{
return new DiscoverySellerModel()
{
Id = sellerProfile.Id,
SocialMediaLinks = sellerProfile.SocialMediaLinks,
Biography = sellerProfile.Biography,
PrepaymentRequired = sellerProfile.PrepaymentRequired
};
}
public static UserSellerProfile ToModel(this SellerProfileModel sellerProfile, UserSellerProfile existingSellerProfile)
{
existingSellerProfile.SocialMediaLinks = sellerProfile.SocialMediaLinks;
existingSellerProfile.Biography = sellerProfile.Biography;
existingSellerProfile.PrepaymentRequired = sellerProfile.PrepaymentRequired;
return existingSellerProfile;
}
}

View File

@ -0,0 +1,11 @@
namespace comissions.app.api.Models.SellerProfileRequest;
public class SellerProfileRequestModel
{
public int Id { get; set; }
public DateTime RequestDate { get; set; }
public string UserId { get; set; }
public bool Accepted { get; set; }
public virtual database.Entities.User User { get; set; } = null!;
}

View File

@ -0,0 +1,15 @@
namespace comissions.app.api.Models.SellerProfileRequest;
public static class SellerProfileRequestModelExtensions
{
public static SellerProfileRequestModel ToModel(this database.Entities.SellerProfileRequest sellerProfileRequest)
{
return new SellerProfileRequestModel()
{
Id = sellerProfileRequest.Id,
UserId = sellerProfileRequest.UserId,
RequestDate = sellerProfileRequest.RequestDate,
Accepted = sellerProfileRequest.Accepted
};
}
}

View File

@ -0,0 +1,8 @@
namespace comissions.app.api.Models.SellerService;
public class SellerServiceCreateModel
{
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
}

View File

@ -0,0 +1,11 @@
namespace comissions.app.api.Models.SellerService;
public class SellerServiceModel
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public string Description { get; set; } = null!;
public double Price { get; set; }
public double AverageRating { get; set; }
public int NumberOfRatings { get; set; }
}

View File

@ -0,0 +1,26 @@
namespace comissions.app.api.Models.SellerService;
public static class SellerServiceModelExtensions
{
public static SellerServiceModel ToModel(this database.Entities.SellerService sellerProfileRequest)
{
double avgRating = 0;
int reviewCount = 0;
var ratings = sellerProfileRequest.Reviews;
if (ratings.Any())
{
avgRating = ratings.Average(x => x.Rating);
reviewCount = sellerProfileRequest.Reviews.Count;
}
return new SellerServiceModel()
{
Id = sellerProfileRequest.Id,
Name = sellerProfileRequest.Name,
Description = sellerProfileRequest.Description,
Price = sellerProfileRequest.Price,
AverageRating = avgRating,
NumberOfRatings = reviewCount
};
}
}

View File

@ -0,0 +1,8 @@
namespace comissions.app.api.Models.SellerService;
public class SellerServiceUpdateModel
{
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace comissions.app.api.Models.User;
public class UserInfoModel
{
public string Id { get; set; } = string.Empty;
public string DisplayName { get; init; } = string.Empty;
public string Biography { get; init; } = string.Empty;
public string Email { get; init; } = string.Empty;
}

View File

@ -0,0 +1,21 @@
namespace comissions.app.api.Models.User;
public static class UserInfoModelExtensions
{
public static UserInfoModel ToModel(this database.Entities.User user)
{
return new()
{
Id = user.Id,
DisplayName = user.DisplayName,
Biography = user.Biography,
Email = user.Email
};
}
public static database.Entities.User ToEntity(this UserInfoUpdateModel user, database.Entities.User existingUser)
{
existingUser.DisplayName = user.DisplayName;
existingUser.Biography = user.Biography;
return existingUser;
}
}

View File

@ -0,0 +1,7 @@
namespace comissions.app.api.Models.User;
public class UserInfoUpdateModel
{
public string DisplayName { get; init; } = string.Empty;
public string Biography { get; init; } = string.Empty;
}

View File

@ -0,0 +1,167 @@
using System.Reflection;
using System.Security.Claims;
using comissions.app.api.Middleware;
using comissions.app.api.Middleware.Authentication;
using comissions.app.api.Services.Payment;
using comissions.app.api.Services.Storage;
using ArtPlatform.Database;
using Auth0.AspNetCore.Authentication;
using comissions.app.database;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.FileProviders;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddSingleton<IStorageService,ImgCdnStorageServiceProvider>();
builder.Services.AddSingleton<IPaymentService,StripePaymentServiceProvider>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddDbContext<ApplicationDbContext>();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "API Documentation",
Version = "v1.0",
Description = ""
});
options.ResolveConflictingActions(x => x.First());
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
BearerFormat = "JWT",
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow
{
TokenUrl = new Uri($"{builder.Configuration.GetValue<string>("Auth0:Domain")}oauth/token"),
AuthorizationUrl = new Uri($"{builder.Configuration.GetValue<string>("Auth0:Domain")}authorize?audience={builder.Configuration.GetValue<string>("Auth0:Audience")}"),
Scopes = new Dictionary<string, string>
{
{ "openid", "OpenId" },
{ "email", "Email" },
{ "profile", "Profile" },
{ "read:user", "Read your profile information." },
{ "write:user", "Update your profile information." },
{ "read:billing-information", "Read your billing information." },
{ "write:billing-information", "Update your billing information." },
{ "read:seller-profile", "Read your seller profile information."},
{ "write:seller-profile", "Update your seller profile information."},
{ "write:seller-profile-request", "Accept seller profile requests."},
{ "read:seller-profile-request", "Read seller profile requests."},
{ "read:seller-service", "Read services on your seller profile."},
{ "write:seller-service", "Update services on your seller profile."},
{ "write:orders", "Create new orders and take action against existing ones."},
{ "read:orders", "View your orders."},
{ "read:seller-orders", "View orders on your seller profile."},
{ "write:seller-orders", "Update orders on your seller profile."}
}
}
}
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
},
new[] { "openid", "email", "profile" }
}
});
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
});
builder.Services.AddControllers()
.AddJsonOptions(options=>
options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles
);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.Authority = $"{builder.Configuration.GetValue<string>("Auth0:Domain")}";
options.Audience = $"{builder.Configuration.GetValue<string>("Auth0:Audience")}";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = ClaimTypes.NameIdentifier,
RoleClaimType = ClaimTypes.Role
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("admin", policy => policy.RequireClaim(ClaimTypes.Role, "Admin"));
options.AddPolicy("read:user", policy => policy.Requirements.Add(new
HasScopeRequirement("read:user", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("write:user", policy => policy.Requirements.Add(new
HasScopeRequirement("write:user", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("read:billing-information", policy => policy.Requirements.Add(new
HasScopeRequirement("read:billing-information", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("write:billing-information", policy => policy.Requirements.Add(new
HasScopeRequirement("write:billing-information", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("read:seller-profile", policy => policy.Requirements.Add(new
HasScopeRequirement("read:seller-profile", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("write:seller-profile", policy => policy.Requirements.Add(new
HasScopeRequirement("write:seller-profile", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("read:seller-profile-request", policy => policy.Requirements.Add(new
HasScopeRequirement("read:seller-profile-request", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("write:seller-profile-request", policy => policy.Requirements.Add(new
HasScopeRequirement("write:seller-profile-request", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("read:seller-service", policy => policy.Requirements.Add(new
HasScopeRequirement("read:seller-service", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("write:seller-service", policy => policy.Requirements.Add(new
HasScopeRequirement("write:seller-service", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("write:orders", policy => policy.Requirements.Add(new
HasScopeRequirement("write:orders", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("read:orders", policy => policy.Requirements.Add(new
HasScopeRequirement("read:orders", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("read:seller-orders", policy => policy.Requirements.Add(new
HasScopeRequirement("read:seller-orders", builder.Configuration.GetValue<string>("Auth0:Domain"))));
options.AddPolicy("write:seller-orders", policy => policy.Requirements.Add(new
HasScopeRequirement("write:seller-orders", builder.Configuration.GetValue<string>("Auth0:Domain"))));
});
builder.Services.AddSingleton<IAuthorizationHandler, HasScopeHandler>();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI(settings =>
{
if (app.Environment.IsDevelopment())
{
settings.OAuthClientId(builder.Configuration.GetValue<string>("Auth0:ClientId"));
settings.OAuthClientSecret(builder.Configuration.GetValue<string>("Auth0:ClientSecret"));
settings.OAuthUsePkce();
}
});
var defaultFilesOptions = new DefaultFilesOptions();
defaultFilesOptions.DefaultFileNames.Clear();
defaultFilesOptions.DefaultFileNames.Add("index.html"); // replace 'yourf
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMiddleware<UserMiddleware>();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:35549",
"sslPort": 44383
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "",
"applicationUrl": "http://localhost:5290",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7148;http://localhost:5290",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,12 @@
using ArtPlatform.Database.Entities;
namespace comissions.app.api.Services.Payment;
public interface IPaymentService
{
public string CreateCustomer();
string CreateSellerAccount();
string CreateSellerAccountOnboardingUrl(string accountId);
bool SellerAccountIsOnboarded(string accountId);
string ChargeForService(int orderSellerServiceId, string? sellerStripeAccountId, double orderPrice);
}

View File

@ -0,0 +1,130 @@
using ArtPlatform.Database.Entities;
using Stripe;
namespace comissions.app.api.Services.Payment;
public class StripePaymentServiceProvider:IPaymentService
{
private readonly IConfiguration _configuration;
private readonly string _apiKey;
public StripePaymentServiceProvider(IConfiguration configuration)
{
_configuration = configuration;
_apiKey = _configuration.GetValue<string>("Stripe:ApiKey");
StripeConfiguration.ApiKey = _apiKey;
}
public string CreateCustomer()
{
var options = new CustomerCreateOptions
{
Name = "Jenny Rosen",
Email = "jennyrosen@example.com",
};
var service = new CustomerService();
var customer = service.Create(options);
return customer.Id;
}
// public string ChargeCustomer(string customerId, int amount)
// {
// var options = new PaymentIntentCreateOptions
// {
// Amount = amount,
// Currency = "usd",
// AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
// {
// Enabled = true,
// },
// };
// var requestOptions = new RequestOptions
// {
// StripeAccount = "{{CONNECTED_ACCOUNT_ID}}",
// };
// var service = new PaymentIntentService();
// var intent = service.Create(options, requestOptions);
// throw new NotImplementedException();
// }
public string CreateSellerAccount()
{
var accountCreateOptions = new AccountCreateOptions { Type = "express",
Capabilities
= new AccountCapabilitiesOptions
{
CardPayments
= new AccountCapabilitiesCardPaymentsOptions { Requested
= true },
Transfers
= new AccountCapabilitiesTransfersOptions { Requested
= true },
} };
var accountService = new AccountService();
var account = accountService.Create(accountCreateOptions);
return account.Id;
}
public string CreateSellerAccountOnboardingUrl(string accountId)
{
var options = new AccountLinkCreateOptions
{
Account = accountId,
RefreshUrl = "https://example.com/reauth",
ReturnUrl = "https://example.com/return",
Type = "account_onboarding",
};
var service = new AccountLinkService();
var url = service.Create(options);
return url.Url;
}
public bool SellerAccountIsOnboarded(string accountId)
{
var service = new AccountService();
var account = service.Get(accountId);
return account.Requirements.CurrentlyDue.Count == 0 && account.ChargesEnabled==true && account.DetailsSubmitted==true;
}
public string ChargeForService(int orderSellerServiceOrderId, string? sellerStripeAccountId,
double orderPrice)
{
var feeAmount = (long)Math.Round((orderPrice*0.05) * 100);
var options = new Stripe.Checkout.SessionCreateOptions
{
LineItems = new List<Stripe.Checkout.SessionLineItemOptions> {
new Stripe.Checkout.SessionLineItemOptions
{
PriceData = new Stripe.Checkout.SessionLineItemPriceDataOptions
{
UnitAmount = (long)Math.Round(orderPrice * 100),
Currency = "usd",
ProductData = new Stripe.Checkout.SessionLineItemPriceDataProductDataOptions
{
Name = "Comission Service",
},
},
Quantity = 1,
},
},
PaymentIntentData = new Stripe.Checkout.SessionPaymentIntentDataOptions
{
ApplicationFeeAmount = feeAmount,
},
Mode = "payment",
SuccessUrl = "https://example.com/success",
CancelUrl = "https://example.com/failure",
Metadata = new Dictionary<string, string>()
{
["orderId"] = orderSellerServiceOrderId.ToString()
}
};
var requestOptions = new RequestOptions
{
StripeAccount = sellerStripeAccountId
};
var service = new Stripe.Checkout.SessionService();
var session = service.Create(options, requestOptions);
return session.Url;
}
}

View File

@ -0,0 +1,7 @@
namespace comissions.app.api.Services.Storage;
public interface IStorageService
{
public Task<string> UploadImageAsync(IFormFile file, string fileName);
public Task<Stream> DownloadImageAsync(string fileRefrence);
}

View File

@ -0,0 +1,53 @@
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace comissions.app.api.Services.Storage
{
public class ImgCdnStorageServiceProvider : IStorageService
{
private readonly HttpClient _client;
private const string ApiKey = "5386e05a3562c7a8f984e73401540836";
public ImgCdnStorageServiceProvider()
{
_client = new HttpClient { BaseAddress = new Uri("https://imgcdn.dev/") };
}
public async Task<string> UploadImageAsync(IFormFile file, string fileName)
{
using var content = new MultipartFormDataContent();
content.Add(new StringContent(ApiKey), "key");
using var fileStream = file.OpenReadStream();
content.Add(new StreamContent(fileStream), "source", fileName);
var response = await _client.PostAsync("api/1/upload", content);
if (!response.IsSuccessStatusCode)
{
throw new Exception("Failed to upload image.");
}
var responseContent = await response.Content.ReadAsStringAsync();
var jsonResponse = JsonDocument.Parse(responseContent);
var imageUrl = jsonResponse.RootElement.GetProperty("image").GetProperty("url").GetString();
return imageUrl;
}
public async Task<Stream> DownloadImageAsync(string fileReference)
{
var response = await _client.GetAsync(fileReference);
if (!response.IsSuccessStatusCode)
{
throw new Exception("Failed to download image.");
}
var stream = await response.Content.ReadAsStreamAsync();
return stream;
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,19 @@
{
"Stripe": {
"WebHookSecret": "whsec_Jdj6IL9NwFO3MtPpry3vJhE72kZjeCtI",
"ApiKey": "sk_test_51OdJ1SLooS0IZqYkx2IdNoLcscm6BisgaUyYVIc5jM1RMmarww2e9hLLQS3Atn6TQi00p9YQkCLGQPhAI2gf9ZSY00HmbQYCvP"
},
"Auth0": {
"Domain": "https://dev-12mb5yq82dow1twh.us.auth0.com/",
"Audience": "https://api.artplatform.com",
"ClientId": "19GWUL7fWFQWEpdFFtlgv2x3kqfSa0ES",
"ClientSecret": "VX5LKeGHeaqKsgNz8Kn1gQ7MSHmwrXJdC2DMjVY82_YHjiRqdPrVNpFFkXBZy8yh"
},
"Logging": {
"LogLevel": {
"Default": "Trace",
"Microsoft.AspNetCore": "Trace"
}
},
"AllowedHosts": "*"
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Globalization.Invariant": true,
"System.Globalization.PredefinedCulturesOnly": true,
"System.Reflection.NullabilityInfoContext.IsSupported": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>ArtPlatform.API</name>
</assembly>
<members>
<member name="M:ArtPlatform.API.Controllers.AdminSellerRequestsController.GetSellerRequests(System.Int32,System.Int32)">
<summary>
Gets a list of all of the requests from users to become a seller.
</summary>
<param name="offset"> The offset to start at.</param>
<param name="pageSize"> The amount of records to return.</param>
<returns>A list of seller profile requests</returns>
</member>
<member name="M:ArtPlatform.API.Controllers.AdminSellerRequestsController.GetSellerRequestsCount">
<summary>
Gets the amount of requests there are from users to become a seller.
</summary>
<returns>The number of requests.</returns>
</member>
<member name="M:ArtPlatform.API.Controllers.AdminSellerRequestsController.AcceptSellerRequest(System.String)">
<summary>
Accepts a request to become a seller from a user.
</summary>
<param name="userId">The ID of the user to accept the request for.</param>
<returns>The new seller profile.</returns>
</member>
</members>
</doc>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More