mirror of
https://github.com/tavo-wasd-gh/conex-builder.git
synced 2025-06-06 11:43:29 -06:00
object storage, misc fixes
This commit is contained in:
parent
906b09eaf7
commit
19b3060514
10 changed files with 394 additions and 172 deletions
5
Makefile
5
Makefile
|
@ -4,11 +4,16 @@ SRC = ${SRCDIR}/main.go \
|
|||
${SRCDIR}/paypal.go \
|
||||
${SRCDIR}/db.go \
|
||||
${SRCDIR}/auth.go \
|
||||
${SRCDIR}/bucket.go \
|
||||
|
||||
GOFILES = ${SRCDIR}/go.sum ${SRCDIR}/go.mod
|
||||
GOMODS = github.com/joho/godotenv \
|
||||
github.com/lib/pq \
|
||||
gopkg.in/gomail.v2 \
|
||||
github.com/aws/aws-sdk-go-v2/aws \
|
||||
github.com/aws/aws-sdk-go-v2/config \
|
||||
github.com/aws/aws-sdk-go-v2/credentials \
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 \
|
||||
|
||||
all: ${BIN} fmt
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ CREATE TABLE sites (
|
|||
phone VARCHAR(20),
|
||||
code VARCHAR(2),
|
||||
title VARCHAR(35) NOT NULL,
|
||||
slogan VARCHAR(100) NOT NULL,
|
||||
slogan VARCHAR(100),
|
||||
banner TEXT,
|
||||
raw JSONB NOT NULL,
|
||||
auth INTEGER,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const savedData = localStorage.getItem('editor_data');
|
||||
const savedData = localStorage.getItem('conex_data');
|
||||
if (savedData) {
|
||||
const parsedData = JSON.parse(savedData);
|
||||
console.log('Loaded parsedData:', parsedData);
|
||||
|
@ -11,14 +11,17 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
const dialog = document.getElementById("dialog");
|
||||
const overlay = document.getElementById("overlay");
|
||||
const menu = document.getElementById("floatingButtons");
|
||||
const checkoutErrorMessage = document.getElementById("checkout-error-message");
|
||||
|
||||
function openDialog() {
|
||||
checkoutErrorMessage.style.display = "none";
|
||||
dialog.style.display = "block";
|
||||
overlay.style.display = "block";
|
||||
menu.style.display = "none";
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
checkoutErrorMessage.style.display = "none";
|
||||
dialog.style.display = "none";
|
||||
overlay.style.display = "none";
|
||||
menu.style.display = "block";
|
||||
|
@ -37,56 +40,60 @@ function saveEditorData() {
|
|||
|
||||
editor.save().then((editor_data) => {
|
||||
const dataToSave = {
|
||||
banner: banner,
|
||||
directory: sanitizeDirectoryTitle(title),
|
||||
banner: banner || '/static/svg/banner.svg',
|
||||
title: title,
|
||||
slogan: slogan,
|
||||
editor_data: editor_data
|
||||
};
|
||||
localStorage.setItem('editor_data', JSON.stringify(dataToSave));
|
||||
localStorage.setItem('conex_data', JSON.stringify(dataToSave));
|
||||
console.log('Editor data saved to localStorage');
|
||||
}).catch((error) => {
|
||||
console.error('Saving failed:', error);
|
||||
});
|
||||
}
|
||||
|
||||
const directoryInput = document.getElementById('title');
|
||||
const statusPopup = document.getElementById('status-popup');
|
||||
const statusMessage = document.getElementById('status-message');
|
||||
|
||||
let typingTimeout;
|
||||
let hideTimeout;
|
||||
|
||||
const directoryInput = document.getElementById('title');
|
||||
directoryInput.addEventListener('input', () => {
|
||||
clearTimeout(typingTimeout);
|
||||
typingTimeout = setTimeout(() => {
|
||||
const directoryName = directoryInput.value.trim();
|
||||
if (directoryName.length > 0) {
|
||||
const sanitizedDirectoryName = sanitizeDirectoryName(directoryName);
|
||||
checkDirectory(sanitizedDirectoryName);
|
||||
const directoryTitle = directoryInput.value.trim();
|
||||
if (directoryTitle.length > 0) {
|
||||
const directory = sanitizeDirectoryTitle(directoryTitle);
|
||||
checkDirectory(directory);
|
||||
} else {
|
||||
hidePopup();
|
||||
}
|
||||
}, 500); // Debounce
|
||||
});
|
||||
|
||||
function sanitizeDirectoryName(name) {
|
||||
return name
|
||||
function sanitizeDirectoryTitle(title) {
|
||||
return title
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/[^a-z0-9\-]/g, '');
|
||||
}
|
||||
|
||||
function checkDirectory(name) {
|
||||
if (name.length < 4) {
|
||||
function checkDirectory(directory) {
|
||||
if (directory.length < 4) {
|
||||
return;
|
||||
}
|
||||
fetch(`/api/directory/${encodeURIComponent(name)}`)
|
||||
if (directory.length > 35) {
|
||||
showPopup(`El título no puede exceder los 35 caracteres`, 'exists');
|
||||
return;
|
||||
}
|
||||
fetch(`/api/directory/${encodeURIComponent(directory)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.exists) {
|
||||
showPopup(`El sitio web conex.one/${name} ya existe`, 'exists');
|
||||
showPopup(`El sitio web conex.one/${directory} ya existe`, 'exists');
|
||||
} else {
|
||||
showPopup(`Se publicará en conex.one/${name}`, 'available');
|
||||
showPopup(`Se publicará en conex.one/${directory}`, 'available');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
|
@ -133,16 +140,28 @@ function hidePopup(popup, status) {
|
|||
}
|
||||
|
||||
document.getElementById('imageUpload').addEventListener('change', function (event) {
|
||||
const savedData = localStorage.getItem('conex_data');
|
||||
const parsedData = savedData ? JSON.parse(savedData) : null;
|
||||
const directory = parsedData?.directory || "temp";
|
||||
const file = event.target.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function (e) {
|
||||
const base64Image = e.target.result;
|
||||
document.getElementById('banner').src = base64Image;
|
||||
saveEditorData();
|
||||
};
|
||||
|
||||
if (file) {
|
||||
reader.readAsDataURL(file);
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('directory', directory);
|
||||
|
||||
fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
}).then(response => response.json()).then(data => {
|
||||
if (data && data.file && data.file.url) {
|
||||
document.getElementById('banner').src = data.file.url;
|
||||
saveEditorData();
|
||||
} else {
|
||||
console.error('Error: Invalid response format', data);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error uploading the image:', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
</label>
|
||||
<img id="banner" name="banner" src="/static/svg/banner.svg" class="banner-image"/>
|
||||
<div class="desc">
|
||||
<input type="text" id="title" name="title" class="input-title" placeholder="[Nombre Ejemplo]">
|
||||
<textarea type="text" id="slogan" name="slogan" class="input-slogan" placeholder="[Slogan llamativo o breve descripción]"></textarea>
|
||||
<input type="text" id="title" name="title" maxlength="35" class="input-title" placeholder="[Nombre Ejemplo]">
|
||||
<textarea type="text" id="slogan" name="slogan" maxlength="100" class="input-slogan" placeholder="[Slogan llamativo o breve descripción]"></textarea>
|
||||
</div>
|
||||
<div id="status-popup" class="status-popup">
|
||||
<span class="close-popup" onclick="hidePopup()">×</span>
|
||||
|
@ -48,11 +48,12 @@
|
|||
<h2>Contratar por $20 al año</h2>
|
||||
<p>Gracias por elegir nuestro servicio para la compra de sitios web. Luego de ser aprobado, su sitio será publicado en menos de 24 horas a partir de la confirmación de tu compra. Utilizaremos los medios de contacto que proporcione para comunicarnos en caso de cualquier inconveniente con la publicación. Si experimenta algún problema, no dude en ponerte en contacto con nosotros a través de los canales:</p>
|
||||
<ul>
|
||||
<li>Correo electrónico: soporte@tusitio.com</li>
|
||||
<li>Teléfono: +1 234 567 890</li>
|
||||
<li>Horario de atención: Lunes a Viernes, de 9:00 a.m. a 6:00 p.m.</li>
|
||||
<li>Correo electrónico: soporte@conex.one</li>
|
||||
<!-- <li>Teléfono: +1 234 567 890</li> -->
|
||||
<li>Horario de atención: L-V: 9:00 a.m. - 6:00 p.m.</li>
|
||||
</ul>
|
||||
<p id="result-message"></p>
|
||||
<div id="checkout-error-message"></div>
|
||||
<div id="checkout-success-message"></div>
|
||||
<div id="paypal-button-container"></div>
|
||||
<button id="cancelDialogButton" type="button">
|
||||
<picture>
|
||||
|
|
202
public/paypal.js
202
public/paypal.js
|
@ -1,105 +1,119 @@
|
|||
paypal.Buttons({
|
||||
style: {
|
||||
shape: "pill",
|
||||
layout: "vertical",
|
||||
color: "black",
|
||||
label: "pay"
|
||||
},
|
||||
async createOrder() {
|
||||
const savedData = JSON.parse(localStorage.getItem('editor_data')) || {};
|
||||
const requestData = {
|
||||
directory: sanitizeDirectoryName(savedData.title),
|
||||
banner: savedData.banner || '/static/svg/banner.svg',
|
||||
title: savedData.title,
|
||||
slogan: savedData.slogan,
|
||||
editor_data: savedData.editor_data
|
||||
};
|
||||
const response = await fetch("/api/orders", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestData),
|
||||
});
|
||||
style: {
|
||||
shape: "pill",
|
||||
layout: "vertical",
|
||||
color: "black",
|
||||
label: "pay"
|
||||
},
|
||||
async createOrder() {
|
||||
const savedData = JSON.parse(localStorage.getItem('conex_data')) || {};
|
||||
const requestData = {
|
||||
directory: savedData.directory,
|
||||
};
|
||||
const response = await fetch("/api/orders", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 409) {
|
||||
resultMessage(`No se puede comprar este sitio, ya existe o tiene un nombre incorrecto. Prueba con un nombre diferente`);
|
||||
} else {
|
||||
resultMessage(`No se puede realizar la compra en este momento`);
|
||||
}
|
||||
console.log(`HTTP Error: ${response.status} - ${response.statusText}`);
|
||||
return;
|
||||
}
|
||||
if (!response.ok) {
|
||||
if (response.status === 409) {
|
||||
checkoutError(`
|
||||
<p>El título "${savedData.title}" es incorrecto, debe cumplir:<br>
|
||||
<ul>
|
||||
<li>Entre 4 y 35 caracteres</li>
|
||||
<li>Debe ser único</li>
|
||||
</ul>
|
||||
</p>
|
||||
`);
|
||||
} else {
|
||||
checkoutError(`<p>No se puede realizar la compra en este momento</p>`);
|
||||
}
|
||||
console.log(`HTTP Error: ${response.status} - ${response.statusText}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const orderData = await response.json();
|
||||
const orderData = await response.json();
|
||||
|
||||
if (orderData.id) {
|
||||
return orderData.id;
|
||||
} else {
|
||||
const errorDetail = orderData?.details?.[0];
|
||||
resultMessage(`No se puede realizar la compra en este momento`);
|
||||
}
|
||||
},
|
||||
async onApprove(data, actions) {
|
||||
try {
|
||||
// @@@ TODO por alguna razon hizo la compra a pesar de que esto estaba mal puesto
|
||||
const requestData = {
|
||||
directory: "gofitness",
|
||||
editor_data: await editor.save()
|
||||
};
|
||||
if (orderData.id) {
|
||||
return orderData.id;
|
||||
} else {
|
||||
const errorDetail = orderData?.details?.[0];
|
||||
checkoutError(`<p>No se puede realizar la compra en este momento</p>`);
|
||||
}
|
||||
},
|
||||
async onApprove(data, actions) {
|
||||
const savedData = JSON.parse(localStorage.getItem('conex_data')) || {};
|
||||
try {
|
||||
const requestData = {
|
||||
directory: savedData.directory,
|
||||
banner: savedData.banner,
|
||||
title: savedData.title,
|
||||
slogan: savedData.slogan,
|
||||
editor_data: savedData.editor_data
|
||||
};
|
||||
|
||||
const response = await fetch(`/api/orders/${data.orderID}/capture`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestData),
|
||||
});
|
||||
const response = await fetch(`/api/orders/${data.orderID}/capture`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestData),
|
||||
});
|
||||
|
||||
const orderData = await response.json();
|
||||
// Three cases to handle:
|
||||
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
|
||||
// (2) Other non-recoverable errors -> Show a failure message
|
||||
// (3) Successful transaction -> Show confirmation or thank you message
|
||||
const orderData = await response.json();
|
||||
// Three cases to handle:
|
||||
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
|
||||
// (2) Other non-recoverable errors -> Show a failure message
|
||||
// (3) Successful transaction -> Show confirmation or thank you message
|
||||
|
||||
const errorDetail = orderData?.details?.[0];
|
||||
const errorDetail = orderData?.details?.[0];
|
||||
|
||||
if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
|
||||
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
|
||||
// recoverable state, per https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
|
||||
return actions.restart();
|
||||
} else if (errorDetail) {
|
||||
// (2) Other non-recoverable errors -> Show a failure message
|
||||
throw new Error(`${errorDetail.description} (${orderData.debug_id})`);
|
||||
} else if (!orderData.purchase_units) {
|
||||
throw new Error(JSON.stringify(orderData));
|
||||
} else {
|
||||
// (3) Successful transaction -> Show confirmation or thank you message
|
||||
// Or go to another URL: actions.redirect('thank_you.html');
|
||||
const transaction =
|
||||
orderData?.purchase_units?.[0]?.payments?.captures?.[0] ||
|
||||
orderData?.purchase_units?.[0]?.payments?.authorizations?.[0];
|
||||
resultMessage(
|
||||
`Estado: <strong>${transaction.status}</strong><br>ID de transacción: ${transaction.id}<br>Luego de una revisión positiva, su sitio será publicado en menos de 24 horas.`,
|
||||
);
|
||||
console.log(
|
||||
"Capture result",
|
||||
orderData,
|
||||
JSON.stringify(orderData, null, 2),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
resultMessage(
|
||||
`Sorry, your transaction could not be processed...<br><br>${error}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
|
||||
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
|
||||
// recoverable state, per https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
|
||||
return actions.restart();
|
||||
} else if (errorDetail) {
|
||||
// (2) Other non-recoverable errors -> Show a failure message
|
||||
throw new Error(`${errorDetail.description} (${orderData.debug_id})`);
|
||||
} else if (!orderData.purchase_units) {
|
||||
throw new Error(JSON.stringify(orderData));
|
||||
} else {
|
||||
// (3) Successful transaction -> Show confirmation or thank you message
|
||||
// Or go to another URL: actions.redirect('thank_you.html');
|
||||
const transaction =
|
||||
orderData?.purchase_units?.[0]?.payments?.captures?.[0] ||
|
||||
orderData?.purchase_units?.[0]?.payments?.authorizations?.[0];
|
||||
checkoutSuccess(`
|
||||
<p>
|
||||
Estado: <strong>${transaction.status}</strong><br>
|
||||
ID de transacción: ${transaction.id}<br>
|
||||
Luego de una revisión positiva, su sitio será publicado en menos de 24 horas.
|
||||
<p>
|
||||
`,);
|
||||
console.log(
|
||||
"Capture result",
|
||||
orderData,
|
||||
JSON.stringify(orderData, null, 2),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
checkoutError(`<p>No se puede realizar la compra en este momento</p>`);
|
||||
}
|
||||
},
|
||||
}).render("#paypal-button-container");
|
||||
|
||||
// Example function to show a result to the user. Your site's UI library can be used instead.
|
||||
function resultMessage(message) {
|
||||
const container = document.querySelector("#result-message");
|
||||
container.innerHTML = message;
|
||||
function checkoutSuccess(message) {
|
||||
const container = document.querySelector("#checkout-success-message");
|
||||
container.style.display = "block";
|
||||
container.innerHTML = message;
|
||||
}
|
||||
|
||||
function checkoutError(message) {
|
||||
const container = document.querySelector("#checkout-error-message");
|
||||
container.style.display = "block";
|
||||
container.innerHTML = message;
|
||||
}
|
||||
|
|
|
@ -236,9 +236,23 @@ button {
|
|||
margin: 1.5em 0 0 0;
|
||||
}
|
||||
|
||||
#result-message {
|
||||
text-decoration: underline;
|
||||
color: var(--warning-color);
|
||||
#checkout-success-message,
|
||||
#checkout-error-message {
|
||||
display: none;
|
||||
padding: 0.8em;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
#checkout-success-message {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
#checkout-error-message {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
#cancelDialogButton {
|
||||
|
@ -320,8 +334,8 @@ button {
|
|||
top: 19%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background-color: #f0f0f0;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
font-size: 16px;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const savedData = localStorage.getItem('editor_data');
|
||||
const savedData = localStorage.getItem('conex_data');
|
||||
const parsedData = savedData ? JSON.parse(savedData) : null;
|
||||
const directory = parsedData?.directory || "temp";
|
||||
|
||||
const editor = new EditorJS({
|
||||
// readOnly: false,
|
||||
|
@ -24,31 +25,13 @@ const editor = new EditorJS({
|
|||
image: {
|
||||
class: ImageTool,
|
||||
config: {
|
||||
uploader: {
|
||||
uploadByFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const base64 = event.target.result;
|
||||
resolve({
|
||||
success: 1,
|
||||
file: {
|
||||
url: base64,
|
||||
},
|
||||
});
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
},
|
||||
uploadByUrl(url) {
|
||||
return Promise.resolve({
|
||||
success: 1,
|
||||
file: {
|
||||
url: url,
|
||||
},
|
||||
});
|
||||
},
|
||||
endpoints: {
|
||||
byFile: `${window.location.origin}/api/upload`,
|
||||
},
|
||||
field: 'file',
|
||||
types: 'image/*',
|
||||
additionalRequestData: {
|
||||
directory: directory,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -194,4 +177,3 @@ const editor = new EditorJS({
|
|||
saveEditorData();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
66
server/bucket.go
Normal file
66
server/bucket.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
)
|
||||
|
||||
func UploadFile(s3Client *s3.Client, endpoint string, bucketName string,
|
||||
publicEndpoint string, fileContent []byte,
|
||||
objectKey string) (string, error) {
|
||||
if _, err := s3Client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: bytes.NewReader(fileContent),
|
||||
}); err != nil {
|
||||
return "", fmt.Errorf("unable to upload file: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", publicEndpoint, objectKey), nil
|
||||
}
|
||||
|
||||
func BucketSizeLimit(apiEndpoint string, apiToken string) error {
|
||||
req, err := http.NewRequest("GET", apiEndpoint, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+apiToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check bucket size 1: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check bucket size 2: %w", err)
|
||||
}
|
||||
|
||||
var bucket struct {
|
||||
Result struct {
|
||||
PayloadSize string `json:"payloadSize"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &bucket)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check bucket size 3: %w", err)
|
||||
}
|
||||
|
||||
payloadBytes, err := strconv.Atoi(bucket.Result.PayloadSize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check bucket size 4: %w", err)
|
||||
}
|
||||
|
||||
if payloadBytes > maxBucketSize {
|
||||
return fmt.Errorf("unable to check bucket size 5: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
24
server/db.go
24
server/db.go
|
@ -37,9 +37,7 @@ func AvailableSite(db *sql.DB, folder string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func RegisterSitePayment(db *sql.DB,
|
||||
capture Capture, directory string, editorData json.RawMessage,
|
||||
) error {
|
||||
func RegisterSitePayment(db *sql.DB, capture Capture, cart ConexData) error {
|
||||
var (
|
||||
// Payment
|
||||
id string
|
||||
|
@ -55,6 +53,12 @@ func RegisterSitePayment(db *sql.DB,
|
|||
email string
|
||||
phone string
|
||||
country string
|
||||
// conex_data
|
||||
directory string
|
||||
title string
|
||||
slogan string
|
||||
banner string
|
||||
editorData json.RawMessage
|
||||
)
|
||||
|
||||
captureData := capture.PurchaseUnits[0].Payments.Captures[0]
|
||||
|
@ -72,6 +76,12 @@ func RegisterSitePayment(db *sql.DB,
|
|||
phone = capture.Payer.Phone.PhoneNumber.NationalNumber
|
||||
country = capture.Payer.Address.CountryCode
|
||||
|
||||
directory = cart.Directory
|
||||
title = cart.Title
|
||||
slogan = cart.Slogan
|
||||
banner = cart.Banner
|
||||
editorData = cart.EditorData
|
||||
|
||||
var pkey int
|
||||
newSite := db.QueryRow(`
|
||||
SELECT id FROM sites WHERE folder = $1
|
||||
|
@ -80,12 +90,12 @@ func RegisterSitePayment(db *sql.DB,
|
|||
if newSite == sql.ErrNoRows {
|
||||
if err := db.QueryRow(`
|
||||
INSERT INTO sites
|
||||
(folder, status, due, name, sur, email, phone, code, raw)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
(folder, status, due, name, sur, email, phone, code, title, slogan, banner, raw)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
RETURNING id
|
||||
`, directory, wstatus, due,
|
||||
name, surname, email, phone, country,
|
||||
editorData).Scan(&pkey); err != nil {
|
||||
name, surname, email, phone, country, title, slogan,
|
||||
banner, editorData).Scan(&pkey); err != nil {
|
||||
return fmt.Errorf("%s: %v", errDBRegisterSite, err)
|
||||
}
|
||||
} else {
|
||||
|
|
123
server/main.go
123
server/main.go
|
@ -1,20 +1,32 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/joho/godotenv"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
const (
|
||||
// Limits
|
||||
maxUploadFileSize = 52428800 // 50MB
|
||||
maxBucketSize = 10737418240 // 10GB
|
||||
// Messages
|
||||
msgClosingDBConn = "Msg: init.go: Closing database connection"
|
||||
msgDBConn = "Msg: init.go: Established database connection"
|
||||
errDBConn = "Fatal: init.go: Connect to database"
|
||||
|
@ -37,8 +49,17 @@ const (
|
|||
errUpdateSite = "Error: main.go: Updating site data"
|
||||
)
|
||||
|
||||
type ConexData struct {
|
||||
Directory string `json:"directory"`
|
||||
Banner string `json:"banner"`
|
||||
Title string `json:"title"`
|
||||
Slogan string `json:"slogan"`
|
||||
EditorData json.RawMessage `json:"editor_data"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
var db *sql.DB
|
||||
var s3Client *s3.Client
|
||||
|
||||
godotenv.Load()
|
||||
var (
|
||||
|
@ -76,11 +97,39 @@ func main() {
|
|||
|
||||
msg(msgDBConn)
|
||||
|
||||
var (
|
||||
bucketName = os.Getenv("BUCKET_NAME")
|
||||
endpoint = os.Getenv("BUCKET_ENDPOINT")
|
||||
accessKey = os.Getenv("BUCKET_ACCESSKEY")
|
||||
secretKey = os.Getenv("BUCKET_SECRETKEY")
|
||||
region = os.Getenv("BUCKET_REGION")
|
||||
publicEndpoint = os.Getenv("BUCKET_PUBLIC_ENDPOINT")
|
||||
apiEndpoint = os.Getenv("BUCKET_API_ENDPOINT")
|
||||
apiToken = os.Getenv("BUCKET_API_TOKEN")
|
||||
)
|
||||
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO(),
|
||||
config.WithRegion(region),
|
||||
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")),
|
||||
config.WithEndpointResolver(aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
|
||||
return aws.Endpoint{
|
||||
URL: endpoint,
|
||||
SigningRegion: region,
|
||||
}, nil
|
||||
})),
|
||||
)
|
||||
if err != nil {
|
||||
fatal(err, errServerStart)
|
||||
}
|
||||
|
||||
s3Client = s3.NewFromConfig(cfg)
|
||||
|
||||
http.HandleFunc("/api/orders", CreateOrderHandler(db))
|
||||
http.HandleFunc("/api/orders/", CaptureOrderHandler(db))
|
||||
http.HandleFunc("/api/update", UpdateSiteHandler(db))
|
||||
http.HandleFunc("/api/confirm", ConfirmChangesHandler(db))
|
||||
http.HandleFunc("/api/directory/", VerifyDirectoryHandler(db))
|
||||
http.HandleFunc("/api/upload", UploadFileHandler(s3Client, endpoint, apiEndpoint, apiToken, bucketName, publicEndpoint))
|
||||
http.Handle("/", http.FileServer(http.Dir("./public")))
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
|
@ -128,6 +177,13 @@ func CreateOrderHandler(db *sql.DB) http.HandlerFunc {
|
|||
httpErrorAndLog(w, err, errReadBody, "Error decoding response")
|
||||
return
|
||||
}
|
||||
|
||||
if len(cart.Directory) > 35 {
|
||||
http.Error(w, "Site already exists", http.StatusConflict)
|
||||
log.Printf("%s: %v", "Site title is too long", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := AvailableSite(db, cart.Directory); err != nil {
|
||||
http.Error(w, "Site already exists", http.StatusConflict)
|
||||
log.Printf("%s: %v", "Site already exists", err)
|
||||
|
@ -155,10 +211,7 @@ func CaptureOrderHandler(db *sql.DB) http.HandlerFunc {
|
|||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
errClientNotice := "Error capturing order"
|
||||
|
||||
var cart struct {
|
||||
Directory string `json:"directory"`
|
||||
EditorData json.RawMessage `json:"editor_data"`
|
||||
}
|
||||
var cart ConexData
|
||||
if err := json.NewDecoder(r.Body).Decode(&cart); err != nil {
|
||||
httpErrorAndLog(w, err, errReadBody, errClientNotice)
|
||||
return
|
||||
|
@ -178,8 +231,7 @@ func CaptureOrderHandler(db *sql.DB) http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
if err := RegisterSitePayment(db, capture, cart.Directory,
|
||||
cart.EditorData); err != nil {
|
||||
if err := RegisterSitePayment(db, capture, cart); err != nil {
|
||||
httpErrorAndLog(w, err, errRegisterSite+": "+cart.Directory, errClientNotice)
|
||||
return
|
||||
}
|
||||
|
@ -280,3 +332,62 @@ func VerifyDirectoryHandler(db *sql.DB) http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
func UploadFileHandler(s3Client *s3.Client, endpoint string, apiEndpoint string,
|
||||
apiToken string, bucketName string, publicEndpoint string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
||||
httpErrorAndLog(w, err, "Unable to parse form", "Unable to parse form")
|
||||
return
|
||||
}
|
||||
directory := r.FormValue("directory")
|
||||
if directory == "" || len(directory) < 4 || len(directory) > 35 {
|
||||
err := fmt.Errorf("invalid directory length")
|
||||
httpErrorAndLog(w, err, "Unable to parse form", "Unable to parse form")
|
||||
return
|
||||
}
|
||||
|
||||
file, fileHeader, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
httpErrorAndLog(w, err, "Unable to get the file", "Unable to get the file")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fileContent, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
httpErrorAndLog(w, err, "Unable to read file", "Unable to read file")
|
||||
return
|
||||
}
|
||||
|
||||
if len(fileContent) > maxUploadFileSize {
|
||||
httpErrorAndLog(w, err, "File too large", "File too large")
|
||||
return
|
||||
}
|
||||
|
||||
if err := BucketSizeLimit(apiEndpoint, apiToken); err != nil {
|
||||
httpErrorAndLog(w, err, "Bucket limit", "Bucket limit")
|
||||
return
|
||||
}
|
||||
|
||||
objectKey := fmt.Sprintf("%s/%s-%s", directory, time.Now().Format("2006-01-02-15-04-05"), fileHeader.Filename)
|
||||
url, err := UploadFile(s3Client, endpoint, bucketName, publicEndpoint, fileContent, objectKey)
|
||||
if err != nil {
|
||||
httpErrorAndLog(w, err, "Unable to upload file", "Unable to upload file")
|
||||
return
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Success int `json:"success"`
|
||||
File struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"file"`
|
||||
}
|
||||
|
||||
response.Success = 1
|
||||
response.File.URL = url
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue