mirror of
https://github.com/Rushilwiz/rounded.git
synced 2025-04-16 09:00:18 -04:00
Merge pull request #1 from Rushilwiz/dev
This commit is contained in:
commit
b96993f73f
BIN
.github/logo.png
vendored
Normal file
BIN
.github/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
||||||
|
.vscode
|
||||||
|
|
||||||
# Django #
|
# Django #
|
||||||
*.log
|
*.log
|
||||||
*.pot
|
*.pot
|
||||||
|
@ -139,4 +141,4 @@ GitHub.sublime-settings
|
||||||
#NextJS
|
#NextJS
|
||||||
.next/
|
.next/
|
||||||
node_modules/
|
node_modules/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
44
README.md
Normal file
44
README.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/rushilwiz/rounded">
|
||||||
|
<img src=".github/logo.png" alt="Logo" width="480px" height="320px">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h1 align="center">rounded.</h1>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inspiration
|
||||||
|
Have you ever gone shopping, and after your big order, you were asked if you wanted to round up for charity? No matter how much you were buying, it was always a mere fraction of what you were paying, and it looked quite nice on your credit card bill. It's so easy for individuals, who wouldn't normally be donating, to take these pennies from their account and send it towards some real good, but we found that there was no real analog for this in the digital realm. As our day-to-day lives begin to move online, shouldn't our goodwill as well?
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
Rounded is a Web3 platform linked to the DeSo blockchain and your finances. First, rounded uses banking APIs like Plaid and PayPal to connect to your online money platforms, and tracks your transaction history. Every time a new transaction is made (i.e. your PayPal balance changes), rounded will ask you if you'd like to round up. When you say yes, it will submit an order with the rounded price and a foundation of your choosing to our backend, which will take $DESO from your wallet and move it into the foundations' wallet. You will then be returned with a TransactionHex as a receipt for your order.
|
||||||
|
|
||||||
|
## How we built it
|
||||||
|
Our frontend was built from the ground up with NextJS and React, creating a comprehensive user interface that held all banking information client-side. This meant that sensitive user data was only being stored clientside, allowing our backend to handle pure order requests. The backend was built on Django and used the DRF (djangorestframework) to create a REST API to handle all requests and talk to the Postgre database. When making transactions on the $DESO blockchain, the backend uses the BitClout V1 DeSo API to generate and sign transactions.
|
||||||
|
|
||||||
|
## Challenges we ran into
|
||||||
|
Unfortunately, $DESO coin is still in Beta, and strong documentation is extremely scarce. We had great lengths of difficulty figuring out how to generate, authenticate, sign, and submit these transactions directly to the blockchain through DeSo's relatively new API, and since none of us had significant experience with controlling crypto wallets with code, it proved a pretty tough feat.
|
||||||
|
|
||||||
|
## Accomplishments that we're proud of
|
||||||
|
We're really happy with how the demo website came out, and eventually, we were able to overcome our issues with trading $DESO, and were able to generate unsigned transactions for demonstration purposes. This project was the culmination of a lot of quick thinking, documentation reading, and learning, and we're all proud of what we were able to take away from it.
|
||||||
|
|
||||||
|
## What we learned
|
||||||
|
Most profoundly was the structure of the $DESO blockchain. We learned about how to create, authenticate, and sign crypto trades on the blockchain, and about the BIP-39 mnemonic spec for generating secure wallets. Along with that, we learned a lot about JWT's and privileged API requests, whether it was to our own API or external ones. (nextjs does not like external API requests)
|
||||||
|
|
||||||
|
## What's next for rounded
|
||||||
|
Philanthropy is always going to be a global issue, and rounded is paving the path for it to move online.
|
||||||
|
|
||||||
|
The next steps for rounded would look like the development of a mobile dashboard along with SMS push notifications every time a transaction is made, asking users if they'd be willing to round up. Along with that, the creation of a browser extension to detect if users are on certain base URLs and when they are making a purchase, ask if they'd like to simultaneously make a donation. We may also open the platform to creators, who, when being paid online, can register with rounded to receive tips through our platform.
|
||||||
|
|
||||||
|
Web3 is the future, we're here to make sure that **everyone** is along for the ride.
|
||||||
|
|
||||||
|
- rounded
|
5
client/.env.local
Normal file
5
client/.env.local
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
AUTH0_SECRET="cd30f2a7f4385cade972fe29aaccfece5ea3efb5482b1f8c1926894ab4f96e58"
|
||||||
|
AUTH0_BASE_URL="http://localhost:3000"
|
||||||
|
AUTH0_ISSUER_BASE_URL="dev-p2ee3ykl.us.auth0.com"
|
||||||
|
AUTH0_CLIENT_ID="b2YFWRCgpZHZFDSA8F1KN2Z05sGC8wgc"
|
||||||
|
AUTH0_CLIENT_SECRET="SCD6CEhPbf-_ls4SK8AohWY0FCPPLwD52hH5Brrbs1ujlH5bUtE2YEl0W6KLTYve"
|
0
client/next.config.js
Normal file
0
client/next.config.js
Normal file
3051
client/package-lock.json
generated
3051
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -12,8 +12,12 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@auth0/nextjs-auth0": "^1.7.0",
|
||||||
"@reduxjs/toolkit": "^1.8.1",
|
"@reduxjs/toolkit": "^1.8.1",
|
||||||
|
"axios": "^0.26.1",
|
||||||
|
"firebase": "^9.6.11",
|
||||||
"next": "^12.1.5",
|
"next": "^12.1.5",
|
||||||
|
"oauth": "^0.9.15",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"react-redux": "^7.2.8",
|
"react-redux": "^7.2.8",
|
||||||
|
|
131
client/src/app/index.css
Normal file
131
client/src/app/index.css
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
@import url("https://fonts.googleapis.com/css2?family=Comfortaa&family=Ubuntu&display=swap");
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-style: none;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: #e61919;
|
||||||
|
animation: color 30s linear infinite;
|
||||||
|
user-select: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.menu {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 2;
|
||||||
|
width: 100vw;
|
||||||
|
height: 50px;
|
||||||
|
background: rgba(0, 0, 0, 0.85);
|
||||||
|
color: #ccc;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 50px;
|
||||||
|
font: 300 32px Comfortaa;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 30px;
|
||||||
|
font-family: Comfortaa;
|
||||||
|
color: #f39;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
background: #f39;
|
||||||
|
color: #fff;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
font: 300 150px Comfortaa;
|
||||||
|
color: #fff;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.hint {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 4;
|
||||||
|
bottom: 10vh;
|
||||||
|
width: 100vw;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
font: 300 30px Comfortaa;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 30px;
|
||||||
|
position: relative;
|
||||||
|
top: 20vh;
|
||||||
|
height: 200vh;
|
||||||
|
font: 16px Ubuntu;
|
||||||
|
}
|
||||||
|
@-moz-keyframes color {
|
||||||
|
0% {
|
||||||
|
background: #e61919;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
background: #80e619;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background: #19e5e6;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
background: #7f19e6;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background: #e61919;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes color {
|
||||||
|
0% {
|
||||||
|
background: #e61919;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
background: #80e619;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background: #19e5e6;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
background: #7f19e6;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background: #e61919;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-o-keyframes color {
|
||||||
|
0% {
|
||||||
|
background: #e61919;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
background: #80e619;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background: #19e5e6;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
background: #7f19e6;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background: #e61919;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes color {
|
||||||
|
0% {
|
||||||
|
background: #e61919;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
background: #80e619;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background: #19e5e6;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
background: #7f19e6;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background: #e61919;
|
||||||
|
}
|
||||||
|
}
|
103
client/src/common/components/Auth.js
Normal file
103
client/src/common/components/Auth.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { useAuth } from "../lib/firebase/authContext";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useState } from "react";
|
||||||
|
// import axios from "axios";
|
||||||
|
export default function AuthComponent() {
|
||||||
|
const { signup, signIn } = useAuth();
|
||||||
|
const router = useRouter();
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [registerUsername, setRegisterUsername] = useState("");
|
||||||
|
const [registerPassword, setRegisterPassword] = useState("");
|
||||||
|
|
||||||
|
async function handleRegisterSubmit(e) {
|
||||||
|
try {
|
||||||
|
const requestOptions = {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: registerUsername,
|
||||||
|
password: registerPassword,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const response = await fetch(
|
||||||
|
"http://localhost:8000/api/profile/create",
|
||||||
|
requestOptions
|
||||||
|
);
|
||||||
|
const dataa = await response.json();
|
||||||
|
console.log(dataa);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function handleSubmit(e) {
|
||||||
|
try {
|
||||||
|
console.log(username);
|
||||||
|
console.log(password);
|
||||||
|
const requestOptions = {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
"username": username, //prettier-ignore
|
||||||
|
"password": password, //prettier-ignore
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
"http://localhost:8000/api/token/",
|
||||||
|
requestOptions
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
localStorage.setItem("token", data.access);
|
||||||
|
localStorage.setItem("username", username);
|
||||||
|
|
||||||
|
// const reqOps = {
|
||||||
|
// method: "GET",
|
||||||
|
// headers: {
|
||||||
|
// "Content-Type": "application/json",
|
||||||
|
// Authorization: `Bearer ${data.access}`,
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// const resp = await fetch("http://localhost:8000/api/foundation/", reqOps);
|
||||||
|
// const data2 = await resp.json();
|
||||||
|
// console.log(data2);
|
||||||
|
|
||||||
|
router.push("/");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Login</h1>
|
||||||
|
<input
|
||||||
|
placeholder="Username"
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button onClick={handleSubmit}>Submit</button>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<h1>OR</h1>
|
||||||
|
<br />
|
||||||
|
<h1>Register</h1>
|
||||||
|
<input
|
||||||
|
placeholder="Username"
|
||||||
|
onChange={(e) => setRegisterUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
onChange={(e) => setRegisterPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button onClick={handleRegisterSubmit}>Submit</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
126
client/src/common/components/Dashboard/Dashboard.js
Normal file
126
client/src/common/components/Dashboard/Dashboard.js
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
export default function DashboardComponent({ clientID, secretID }) {
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
const bearerResponse = await fetch(
|
||||||
|
"https://api.sandbox.paypal.com/v1/oauth2/token",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Accept-Language": "en_US",
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
Authorization:
|
||||||
|
"Basic " +
|
||||||
|
btoa(
|
||||||
|
"AbA-5-a8nI5NbtAr0lrk2HNeJsc9u8W9TSy0zCWVgQKBkdkGTgRS5FRnpE2EuZge0Wvgzj0nRcgQAayY:EI_hNPlvttB2d8vIpqZC-qhuBGjZW_UOcFRJVs2-Axt8QFh1ZZ6j-jk0XYfrpOk7qgHPV7TubhgmuR3k"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
body: "grant_type=client_credentials",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const bearer = await bearerResponse.json();
|
||||||
|
const access_token = bearer.access_token;
|
||||||
|
console.log(access_token);
|
||||||
|
var today = new Date();
|
||||||
|
var date =
|
||||||
|
today.getFullYear() +
|
||||||
|
"-" +
|
||||||
|
(today.getMonth() + 1) +
|
||||||
|
"-" +
|
||||||
|
today.getDate();
|
||||||
|
var time =
|
||||||
|
today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
|
||||||
|
var dateTime = date + "T" + time + "-07:00";
|
||||||
|
const balanceResponse = await fetch(
|
||||||
|
`https://api.sandbox.paypal.com/v1/reporting/balances?currency_code=ALL&as_of_time=${dateTime}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Accept-Language": "en_US",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + access_token,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const balance = await balanceResponse.json();
|
||||||
|
console.log(balance);
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
let orders = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
name: "Order 1",
|
||||||
|
date: "2020-01-01",
|
||||||
|
amount: "100.06",
|
||||||
|
vendor: "Amazon",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
name: "Order 2",
|
||||||
|
date: "2020-02-25",
|
||||||
|
amount: "235.21",
|
||||||
|
vendor: "Walmart",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
name: "Order 3",
|
||||||
|
date: "2020-04-15",
|
||||||
|
amount: "10.89",
|
||||||
|
vendor: "Ebxay",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div classNameName="relative overflow-x-auto shadow-md sm:rounded-lg">
|
||||||
|
<table className="w-full text-sm text-left text-gray-500 dark:text-gray-400">
|
||||||
|
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Order Name
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Date
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Vendor
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
Price
|
||||||
|
</th>
|
||||||
|
<th scope="col" className="px-6 py-3">
|
||||||
|
<span className="sr-only">Edit</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{orders.map((order) => (
|
||||||
|
<tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<th
|
||||||
|
scope="row"
|
||||||
|
className="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{order.name}
|
||||||
|
</th>
|
||||||
|
<td className="px-6 py-4">{order.date}</td>
|
||||||
|
<td className="px-6 py-4">{order.vendor}</td>
|
||||||
|
<td className="px-6 py-4">${order.amount}</td>
|
||||||
|
<td className="px-6 py-4 text-right">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="font-medium text-blue-600 dark:text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
|
Round up for charity?
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import "../app/globals.css";
|
import "../app/globals.css";
|
||||||
|
import "../app/index.css";
|
||||||
|
|
||||||
|
// import { AuthProvider } from "../common/lib/firebase/authContext";
|
||||||
export default function MyApp({ Component, pageProps }) {
|
export default function MyApp({ Component, pageProps }) {
|
||||||
return <Component {...pageProps} />;
|
return <Component {...pageProps} />;
|
||||||
}
|
}
|
||||||
|
|
8
client/src/pages/auth.js
Normal file
8
client/src/pages/auth.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import AuthComponent from "../common/components/Auth";
|
||||||
|
export default function AuthPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AuthComponent />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
12
client/src/pages/dashboard.js
Normal file
12
client/src/pages/dashboard.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import DashboardComponent from "../common/components/Dashboard/Dashboard";
|
||||||
|
export default function Dashboard() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>This is the dashboard page.</h1>
|
||||||
|
<DashboardComponent
|
||||||
|
clientID="AbA-5-a8nI5NbtAr0lrk2HNeJsc9u8W9TSy0zCWVgQKBkdkGTgRS5FRnpE2EuZge0Wvgzj0nRcgQAayY"
|
||||||
|
secretID="EI_hNPlvttB2d8vIpqZC-qhuBGjZW_UOcFRJVs2-Axt8QFh1ZZ6j-jk0XYfrpOk7qgHPV7TubhgmuR3k"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
28
client/src/pages/donate.js
Normal file
28
client/src/pages/donate.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
export default function DonatePage() {
|
||||||
|
const [foundation, setFoundation] = useState([]);
|
||||||
|
useEffect(() => {
|
||||||
|
async function getData() {
|
||||||
|
const reqOps = {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const resp = await fetch("http://localhost:8000/api/foundation/", reqOps);
|
||||||
|
const data = await resp.json();
|
||||||
|
// console.log(data);
|
||||||
|
setFoundation(JSON.stringify(data));
|
||||||
|
console.log(foundation);
|
||||||
|
}
|
||||||
|
// const d = ;
|
||||||
|
getData();
|
||||||
|
// setFoundation(d);
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>This is the donate page.</h1>
|
||||||
|
<p>This page is under construction. Please check back later.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,7 +1,38 @@
|
||||||
|
// import { useAuth } from "../common/lib/firebase/authContext";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
// const { currentUser } = useAuth();
|
||||||
|
const [currentUser, setCurrentUser] = useState(null);
|
||||||
|
useEffect(() => {
|
||||||
|
// Perform localStorage action
|
||||||
|
setCurrentUser(localStorage.getItem("username"));
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div
|
||||||
|
className="logo"
|
||||||
|
data-0="opacity: 1; display: flex"
|
||||||
|
data-99="opacity: 0; display: none"
|
||||||
|
data-100="display: none"
|
||||||
|
>
|
||||||
|
<div className="logo">rounded.</div>
|
||||||
|
</div>
|
||||||
|
<div className="hint" data-0="bottom: 10vh" data-100="bottom: -25px">
|
||||||
|
the future of rounding up, for a better tomorrow
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="menu">rounded.</div>
|
||||||
|
<div className="content">We're rounding up where you can't.</div>
|
||||||
|
|
||||||
<h1>This is HackTJ 2022!</h1>
|
<h1>This is HackTJ 2022!</h1>
|
||||||
|
{currentUser ? (
|
||||||
|
<p>You're logged in! Current username: {currentUser}</p>
|
||||||
|
) : (
|
||||||
|
<p>Not logged in</p>
|
||||||
|
)}
|
||||||
|
<a href="/dashboard">Go to Dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
37
client/src/pages/profile.js
Normal file
37
client/src/pages/profile.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
export default function Profile() {
|
||||||
|
const [wallet, setWallet] = useState("");
|
||||||
|
async function handleSubmit(e) {
|
||||||
|
try {
|
||||||
|
const reqOps = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${localStorage.getItem("token")}`, //prettier-ignore
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
"address": wallet, //prettier-ignore
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const resp = await fetch(
|
||||||
|
"http://localhost:8000/api/profile/add_wallet",
|
||||||
|
reqOps
|
||||||
|
);
|
||||||
|
const data = await resp.json();
|
||||||
|
console.log(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Profile</h1>
|
||||||
|
<h2>Add Wallet</h2>
|
||||||
|
<input
|
||||||
|
placeholder="Address"
|
||||||
|
onChange={(e) => setWallet(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button onClick={handleSubmit}>Submit</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,8 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: ["./src/**/*.{html,js}"],
|
||||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
|
||||||
"./components/**/*.{js,ts,jsx,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
|
1987
client/yarn.lock
Normal file
1987
client/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
18
server/Pipfile
Normal file
18
server/Pipfile
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
django = "*"
|
||||||
|
gunicorn = "*"
|
||||||
|
python-dotenv = "*"
|
||||||
|
djangorestframework = "*"
|
||||||
|
django-cors-headers = "*"
|
||||||
|
djangorestframework-simplejwt = "*"
|
||||||
|
requests = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.9"
|
147
server/Pipfile.lock
generated
Normal file
147
server/Pipfile.lock
generated
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "edc0e33714e869ceb2bf072793d154b82545e005bf908ca2949645b193c13297"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.9"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"asgiref": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0",
|
||||||
|
"sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==3.5.0"
|
||||||
|
},
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
|
||||||
|
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
|
||||||
|
],
|
||||||
|
"version": "==2021.10.8"
|
||||||
|
},
|
||||||
|
"charset-normalizer": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
|
||||||
|
"sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3'",
|
||||||
|
"version": "==2.0.12"
|
||||||
|
},
|
||||||
|
"django": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:07c8638e7a7f548dc0acaaa7825d84b7bd42b10e8d22268b3d572946f1e9b687",
|
||||||
|
"sha256:4e8177858524417563cc0430f29ea249946d831eacb0068a1455686587df40b5"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.0.4"
|
||||||
|
},
|
||||||
|
"django-cors-headers": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a22be2befd4069c4fc174f11cf067351df5c061a3a5f94a01650b4e928b0372b",
|
||||||
|
"sha256:eb98389bf7a2afc5d374806af4a9149697e3a6955b5a2dc2bf049f7d33647456"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.11.0"
|
||||||
|
},
|
||||||
|
"djangorestframework": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee",
|
||||||
|
"sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.13.1"
|
||||||
|
},
|
||||||
|
"djangorestframework-simplejwt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:75323528a7b910843b879194746f0ebc3481c4ad9f354de2dd162160662fb95a",
|
||||||
|
"sha256:cd3ee3aabdc98de104957c136e74ba2a16571b151c88ecd97421fce171a03ea4"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==5.1.0"
|
||||||
|
},
|
||||||
|
"gunicorn": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e",
|
||||||
|
"sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==20.1.0"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||||
|
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3'",
|
||||||
|
"version": "==3.3"
|
||||||
|
},
|
||||||
|
"pyjwt": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41",
|
||||||
|
"sha256:e0c4bb8d9f0af0c7f5b1ec4c5036309617d03d56932877f2f7a0beeb5318322f"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==2.3.0"
|
||||||
|
},
|
||||||
|
"python-dotenv": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f",
|
||||||
|
"sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.20.0"
|
||||||
|
},
|
||||||
|
"pytz": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
|
||||||
|
"sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
|
||||||
|
],
|
||||||
|
"version": "==2022.1"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
|
||||||
|
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.27.1"
|
||||||
|
},
|
||||||
|
"sqlparse": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae",
|
||||||
|
"sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
|
"version": "==0.4.2"
|
||||||
|
},
|
||||||
|
"tzdata": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9",
|
||||||
|
"sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform == 'win32'",
|
||||||
|
"version": "==2022.1"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
|
||||||
|
"sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||||
|
"version": "==1.26.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
0
server/api/__init__.py
Normal file
0
server/api/__init__.py
Normal file
9
server/api/admin.py
Normal file
9
server/api/admin.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
admin.site.register(models.Consumer)
|
||||||
|
admin.site.register(models.Foundation)
|
||||||
|
admin.site.register(models.Wallet)
|
||||||
|
admin.site.register(models.FoundationOrder)
|
6
server/api/apps.py
Normal file
6
server/api/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'api'
|
67
server/api/migrations/0001_initial.py
Normal file
67
server/api/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# Generated by Django 4.0.4 on 2022-04-16 20:35
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Consumer',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('access_token', models.CharField(max_length=100)),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Consumers',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Foundation',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100)),
|
||||||
|
('description', models.TextField()),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Foundations',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Wallet',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('address', models.CharField(max_length=100)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Wallets',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='FoundationOrder',
|
||||||
|
fields=[
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=9)),
|
||||||
|
('uuid', models.UUIDField(primary_key=True, serialize=False, unique=True)),
|
||||||
|
('consumer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='foundation_orders', to='api.consumer')),
|
||||||
|
('foundation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='foundation_orders', to='api.foundation')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Foundation Orders',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='foundation',
|
||||||
|
name='wallet',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.wallet'),
|
||||||
|
),
|
||||||
|
]
|
20
server/api/migrations/0002_consumer_wallet.py
Normal file
20
server/api/migrations/0002_consumer_wallet.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.0.4 on 2022-04-16 20:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consumer',
|
||||||
|
name='wallet',
|
||||||
|
field=models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='api.wallet'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
19
server/api/migrations/0003_alter_consumer_wallet.py
Normal file
19
server/api/migrations/0003_alter_consumer_wallet.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 4.0.4 on 2022-04-16 21:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0002_consumer_wallet'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='consumer',
|
||||||
|
name='wallet',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.wallet'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 4.0.4 on 2022-04-17 13:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0003_alter_consumer_wallet'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='consumer',
|
||||||
|
name='access_token',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='foundation',
|
||||||
|
name='hex',
|
||||||
|
field=models.CharField(blank=True, max_length=200, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='foundation',
|
||||||
|
name='description',
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='foundation',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True),
|
||||||
|
),
|
||||||
|
]
|
0
server/api/migrations/__init__.py
Normal file
0
server/api/migrations/__init__.py
Normal file
51
server/api/models.py
Normal file
51
server/api/models.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
class Wallet (models.Model):
|
||||||
|
address = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.address[0:4]
|
||||||
|
# return f'{self.consumer.user.username}\'s wallet'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Wallets"
|
||||||
|
|
||||||
|
class Consumer (models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
|
wallet = models.OneToOneField(Wallet, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
|
phone = models.CharField(max_length=32, null=True, blank=True),
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.user.username}\'s profile'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Consumers"
|
||||||
|
|
||||||
|
class Foundation (models.Model):
|
||||||
|
wallet = models.OneToOneField(Wallet, on_delete=models.CASCADE)
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
description = models.TextField(null=True, blank=True)
|
||||||
|
hex = models.CharField(max_length=200, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.user.username}\'s foundation'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Foundations"
|
||||||
|
|
||||||
|
class FoundationOrder (models.Model):
|
||||||
|
consumer = models.ForeignKey(Consumer, related_name='foundation_orders', on_delete=models.CASCADE)
|
||||||
|
foundation = models.ForeignKey(Foundation, related_name='foundation_orders', on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
price = models.DecimalField(max_digits=9, decimal_places=2)
|
||||||
|
uuid = models.UUIDField(primary_key=True, unique=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Foundation Orders"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.consumer.user.username}\'s Order to {self.foundation.name} for {self.price}'
|
62
server/api/serializers.py
Normal file
62
server/api/serializers.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
from operator import mod
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('username', 'email', 'first_name', 'last_name')
|
||||||
|
|
||||||
|
class ConsumerCreateSerializer(serializers.ModelSerializer):
|
||||||
|
password = serializers.CharField(write_only=True)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
user = User.objects.create_user(**validated_data)
|
||||||
|
user.set_password(validated_data['password'])
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
consumer = models.Consumer.objects.create(user=user)
|
||||||
|
consumer.save()
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.User
|
||||||
|
fields = ('username', 'password', 'email', 'first_name', 'last_name')
|
||||||
|
|
||||||
|
class FoundationSerializer(serializers.ModelSerializer):
|
||||||
|
user = UserSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Foundation
|
||||||
|
fields = ('user', 'name', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
class ConsumerOrderSerializer(serializers.ModelSerializer):
|
||||||
|
foundation = FoundationSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.FoundationOrder
|
||||||
|
fields = ('price', 'uuid', 'foundation')
|
||||||
|
|
||||||
|
|
||||||
|
class ConsumerSerializer(serializers.ModelSerializer):
|
||||||
|
user = UserSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Consumer
|
||||||
|
fields = ('user', 'access_token', 'foundation_orders')
|
||||||
|
|
||||||
|
class FoundationOrderSerializer(serializers.ModelSerializer):
|
||||||
|
consumer = ConsumerSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.FoundationOrder
|
||||||
|
fields = ('price', 'uuid', 'consumer')
|
||||||
|
|
||||||
|
class FoundationOrderCreateSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.FoundationOrder
|
||||||
|
fields = ('price', 'uuid', 'foundation')
|
3
server/api/tests.py
Normal file
3
server/api/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
20
server/api/urls.py
Normal file
20
server/api/urls.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
from rest_framework_simplejwt.views import (
|
||||||
|
TokenObtainPairView,
|
||||||
|
TokenRefreshView,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('profile/create', views.ConsumerCreate.as_view()),
|
||||||
|
path('profile/add_wallet', views.ConsumerAddWallet.as_view()),
|
||||||
|
path('profile/update', views.ConsumerUpdate.as_view()),
|
||||||
|
path('profile/', views.ConsumerDetail.as_view()),
|
||||||
|
path('foundation/', views.FoundationViewSet.as_view({'get': 'list', 'post': 'create'})),
|
||||||
|
path('foundation/<int:pk>/', views.FoundationViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
|
||||||
|
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
|
||||||
|
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
||||||
|
path('generate_order/', views.generate_order),
|
||||||
|
]
|
77
server/api/views.py
Normal file
77
server/api/views.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from .models import *
|
||||||
|
from .serializers import *
|
||||||
|
|
||||||
|
from rest_framework import status, permissions
|
||||||
|
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
from rest_framework.generics import CreateAPIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
class FoundationViewSet(ModelViewSet):
|
||||||
|
permission_classes = [permissions.AllowAny]
|
||||||
|
queryset = models.Foundation.objects.all()
|
||||||
|
serializer_class = FoundationSerializer
|
||||||
|
|
||||||
|
class ConsumerOrderViewSet(ModelViewSet):
|
||||||
|
queryset = ''
|
||||||
|
serializerClass = ConsumerOrderSerializer
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
queryset = request.user.consumer.foundation_orders.all()
|
||||||
|
serializer = ConsumerOrderSerializer(queryset, many=True)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsumerCreate(CreateAPIView):
|
||||||
|
model = User
|
||||||
|
permission_classes = [permissions.AllowAny]
|
||||||
|
serializer_class = ConsumerCreateSerializer
|
||||||
|
|
||||||
|
class ConsumerDetail(APIView):
|
||||||
|
def get(self, request, format=None):
|
||||||
|
profile = request.user.consumer
|
||||||
|
serializer = ConsumerSerializer(profile)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
class ConsumerUpdate(APIView):
|
||||||
|
def post(self, request, format=None):
|
||||||
|
profile = request.user.consumer
|
||||||
|
profile.phone = request.data['phone']
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
class ConsumerAddWallet(APIView):
|
||||||
|
def post(self, request, format=None):
|
||||||
|
profile = request.user.consumer
|
||||||
|
profile.wallet = Wallet.objects.create(address=request.data['address'])
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
def generate_order(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
foundation = User.objects.get(username=request.POST['foundation']).foundation
|
||||||
|
price = int(request.POST['price'])
|
||||||
|
uuid = uuid4()
|
||||||
|
order = FoundationOrder.objects.create(consumer=request.user.consumer, foundation=foundation, price=price, uuid=uuid)
|
||||||
|
|
||||||
|
rate = int(requests.get("https://node.deso.org/api/v0/get-exchange-rate").json()["USDCentsPerBitCloutExchangeRate"])
|
||||||
|
|
||||||
|
price_in_nanos = (((10**9) * price) / rate)
|
||||||
|
|
||||||
|
options = {
|
||||||
|
"SenderPublicKeyBase58Check": request.user.consumer.wallet.address,
|
||||||
|
"RecipientPublicKeyOrUsername": foundation.wallet.address,
|
||||||
|
"AmountNanos": price_in_nanos
|
||||||
|
}
|
||||||
|
|
||||||
|
hex = request.post('https://node.deso.org/api/v0/send-deso', json=options).json()["TransactionHex"]
|
||||||
|
foundation.hex = hex
|
||||||
|
|
||||||
|
foundation.save()
|
||||||
|
|
2
server/config/.env.sample
Normal file
2
server/config/.env.sample
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
SECRET_KEY=notsosecret
|
||||||
|
DEBUG=True
|
|
@ -12,6 +12,10 @@ https://docs.djangoproject.com/en/4.0/ref/settings/
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
@ -20,26 +24,36 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = 'django-insecure-ioum!dn#uh5t3yn_15j)4qtjp6wyl24-xx*mv6d1kl&wk_-mx1'
|
SECRET_KEY = str(os.getenv('SECRET_KEY'))
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = str(os.getenv("DEBUG")).lower() == "true"
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
if DEBUG:
|
||||||
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
else:
|
||||||
|
ALLOWED_HOSTS = ["api.roundedapp.tech"]
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
'api',
|
||||||
|
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
|
||||||
|
'corsheaders',
|
||||||
|
'rest_framework'
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
@ -68,16 +82,39 @@ TEMPLATES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'config.wsgi.application'
|
WSGI_APPLICATION = 'config.wsgi.application'
|
||||||
|
CORS_ORIGIN_ALLOW_ALL=True
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
if DEBUG:
|
||||||
'default': {
|
DATABASES = {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'default': {
|
||||||
'NAME': BASE_DIR / 'db.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else:
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
|
'NAME': 'rounded',
|
||||||
|
'USER': 'rounded',
|
||||||
|
'PASSWORD': 'rounded',
|
||||||
|
'HOST': 'localhost',
|
||||||
|
'PORT': '5432',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
|
),
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,6 +153,8 @@ USE_TZ = True
|
||||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
STATIC_URL = 'static/'
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
|
||||||
|
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
||||||
|
|
|
@ -14,8 +14,9 @@ Including another URLconf
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
path('api/', include('api.urls')),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user