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
GitVersion.ymlREADME.md
docs
src
.dockerignore
.idea
.idea.ArtPlatform.Database/.idea
.idea.ArtPlatform/.idea
dataSources/e4a3d98b-bfa1-4036-a59a-24d9e362740e/storage_v2/_src_/database
workspace.xml
.idea.comissions.app/.idea
comissions.app.api
Auth0.Login.CustomAction
Controllers
Dockerfile
Extensions
Middleware
Models
Program.cs
Properties
Services
appsettings.Development.jsonappsettings.json
bin/Debug/net8.0

11
GitVersion.yml Normal 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

@ -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.

@ -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

(image error) Size: 85 KiB

326
docs/database_design.drawio Normal 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

(image error) Size: 865 KiB

221
docs/userflow_design.drawio Normal 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

(image error) Size: 379 KiB

25
src/.dockerignore Normal 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

@ -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>

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

@ -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

@ -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

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

@ -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>

@ -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>

@ -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>

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

@ -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>

@ -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]);
}
}
}}

@ -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);
}
}

@ -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);
}
}

@ -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();
}
}

@ -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();
}
}

@ -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);
}
}

@ -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");
}
}

@ -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");
}
}

@ -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);
}
}

@ -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();
}
}

@ -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);
}
}

@ -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"]

@ -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;
}
}

@ -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;
}
}

@ -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));
}
}

@ -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);
}
}

@ -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; }
}

@ -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; }
}

@ -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; }
}

@ -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()
};
}
}

@ -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; }
}

@ -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
};
}
}

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

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

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

@ -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
};
}
}

@ -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; }
}

@ -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;
}
}

@ -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!;
}

@ -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
};
}
}

@ -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; }
}

@ -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; }
}

@ -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
};
}
}

@ -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; }
}

@ -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;
}

@ -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;
}
}

@ -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;
}

@ -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();

@ -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"
}
}
}
}

@ -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);
}

@ -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;
}
}

@ -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);
}

@ -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;
}
}
}

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

@ -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

@ -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
}
}
}

@ -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.

Binary file not shown.

Binary file not shown.

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