Base
This commit is contained in:
commit
ffb1739611
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
data
|
203
article.md
Normal file
203
article.md
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
# Hyper PyFleX
|
||||||
|
|
||||||
|
Thx to https://hypermedia.systems/ for the book.
|
||||||
|
Really nice.
|
||||||
|
|
||||||
|
## Me
|
||||||
|
No web dev before
|
||||||
|
Like coding, AI, casual geek
|
||||||
|
Was a full python dev, never really touched anything else
|
||||||
|
Wanted to do SaaS, like software
|
||||||
|
Didn't do CS, did Thermal and Energy Engineering
|
||||||
|
First wanted to become data scientist then engineer then software dev then full stack dev. Guess I want to do everything so full stack make sense
|
||||||
|
|
||||||
|
## Intro
|
||||||
|
Don't wanted to learn JS so search and found HTMX, was a revelation. I couldn't understand why everybody use JS client if this is available. I mean to do for some highly performant software. But I use python as a backend so, yeah. I have the phylosiphie of simplicity. SOmething simple is something durable, reparaible, upgradable, understadable, ect.
|
||||||
|
|
||||||
|
Call me crazy but I don't comment my code, or few. I do more docs than comments. Why ? Because my code need to be my comment. I need to be able to understand what this code do by looking at it. Simplicity is readable, understable. So I can upgrade it or repaire it, ect. I guess because I didn't do a CS degree, I don't have strong beleive in comment or stuff said at school.
|
||||||
|
|
||||||
|
So when I saw some JS code and started to do some basic stuff using chatGPT to give me a script that add a row to a table. I made it work, my app was pretty good, not a lot JS. But WHAT A PAIN IN THE ASS. And look at this shit! How ugly it is. I can't read it. I mean I know, I need to learn JS, code, blablabla to understand it like I understand python, blablabla. I KNOW, I don't WANT to lear JS. Ear me out, I am not saying that JS is bad at all, I'm sure it's great, but I don't WANT to know, I want to stick with what I know, my beloved python and my easy pandas, numpy and stuff. Web dev suck rn because of that in my opinion, you need to learn JS, and not small Java, did you see the size of Java client for some basic stuff ? Crazy !!! JS is the popular because you are force to use this shit. Anyway, no djugi here right ?
|
||||||
|
|
||||||
|
Anyway, I did the first version of my app using streamlit. Streamlit, if you don't know, it's great, it's easy, it's python. I know, I know, I'm biais, bite me. I did a really great app with it, I wouln't be ashame to share it as if and try to sell it. Specially if I needed to learn JS lol, no way, prefer stick to streamlit than learning JS. That's why thx god HTMX. So for this first version, I used mongoDB as a DB, and I like it. I first took it for the vector seach, that I used it in the v1! But removed to focus on the chat, but I had a all library, import doc, semantic search and shit. And I was already a bit using the idea of HTMX in some way.
|
||||||
|
|
||||||
|
All data was in mongoDB, and nothing store locally, everytime I reload the script I get the data from mongo and display the messages. Some peoples would tell me "but that useless and you use databse cpu, you should add the message to the db and update a list of message in your client app and then trigger an event on just the chat to blablabla" Well no, I'm not doing that. I keep it simple. And it was the best idea. At first I was worry "it's stupid, it will broke so fast". But guess what, quite the oposite. In fact it was so easy to add features !!! For example I want to add a message to the conversation, well I add it to the db and reload the script, that's it. So I can do it from anywere, at anytime, whithout breaking anything. Noting in the style of "but this small thing need to be change because of this special scenario because I am in this part of the app and not here, blablabla" Nothing, Easy. Yes I end up doing client -> server -> db -> server -> client
|
||||||
|
|
||||||
|
So HTMX feeled a bit similare. Instead of Client + Server, it is Server + Screen. No client, that is the core idea of Hyper PyFleX. Easy, no JS, no client/server shit.
|
||||||
|
And the beauty is that Hyper PyFleX is obviously all about HTMX. So you can do like Hyper GinX for Go + Gin + HTMX or Hyper RailX, ect
|
||||||
|
The Hyper in all of them is for Hypermedia in HTML, whick is the heart of how internet and webbrowser work and mostly display and organise things. (Recommand https://hypermedia.systems/) TODO ADD the acrynyme from the books
|
||||||
|
Another huhg advantages of using HTMX instead of JS is that you can use all HTML library, like alpine.js, ect.
|
||||||
|
|
||||||
|
The core idea being that the server send all the browser need to know t odisplay the app. It doesn't need to understand what the app is or do, all of that is either in the HTML, in our head or in the server.
|
||||||
|
When I click on a button to remove something, the client don't understand that I want to remove something. It just understand that I want to change this part. And if the change happend to be an empty HTML, well it's been "removed".
|
||||||
|
Compare to the API phylosophy. The server send just the data, but then the server need a function that understand and interpret the data.
|
||||||
|
|
||||||
|
For example I want to display a username and an email. If my API send this:
|
||||||
|
```html
|
||||||
|
<h1>Adrien</h1>
|
||||||
|
<p>adrien.bouvais@blabla.com<p>
|
||||||
|
```
|
||||||
|
In this response, there is everything the browser need to know to display. The name is an header and above the mail, ect. I can update, put the email next to the name, ect.
|
||||||
|
|
||||||
|
Now let's see the current approche. Usuqlly server send JSON to the client and then the client need to do stuff.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name" : "Adrien",
|
||||||
|
"email" : "adrien.bouvais@blabla.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
But with that, you don't know how to display it. You need a new JS function to pass from one to another.
|
||||||
|
|
||||||
|
Why ? I already did the logic server side of getting the data, ect. Why do I need to split some part in JS and do some part twice ?
|
||||||
|
|
||||||
|
## part 0 - table
|
||||||
|
|
||||||
|
At my work, I was task to implement an app. The app is pretty simple. You have a table that is a list of test done on a database. You can see the number of errors. For each test there is a button to see the detail.
|
||||||
|
|
||||||
|
Let's take an easy example, I have a list of client, I check if any have more than 5 addresses. If so, when I click on the +, I get a new table that list all client with more than 5 addresses. And there is multiple test like that.
|
||||||
|
Pretty simple right ? Well no, obviously.
|
||||||
|
|
||||||
|
Man, I stuggeled so hard to just add one + button, and then if I do that, it change that, and that and blablabla and it lag and it's not center. God damm, that why I don't wanted JS, I don't know JS. I did a script to exclude a row of the second table and it would react... What a mistake, I think I touched the ultimate use case here, how easy it is in HTMX compare to the other way is just incridible. And now the ultimate roast for JS. Yes JS on client side is in theory more efficient... in theory. But I don't know what I'm am doing here! chatGPT gave me this and it work but god, I use it for python and sometime it give me somecrazy shit. Just to say that it is if you know what you are doing. Way better to stick to the language you know, here you can do someting opti, here you understand.
|
||||||
|
|
||||||
|
Let's go into a bit of code. So I have my Flask app running
|
||||||
|
```python
|
||||||
|
from flask import Flask, render_template, request
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('demo1.html')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
That look like that:
|
||||||
|
TODO Add image demo1
|
||||||
|
|
||||||
|
Easy enough. A list and a form with 2 input and a button.
|
||||||
|
|
||||||
|
Obviously I want to do 2 things, be able to add row and delete it. With JS, a bit of a pain in the ass. I know that some people will come with "just import react and react a React component then link it to the react hook to auto react to action of the user react blablabla". But let's ee how HTMX would to it. First the table itself:
|
||||||
|
```html
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Age</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><button>Delete</button></td>
|
||||||
|
<td>John</td>
|
||||||
|
<td>20</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><button>Delete</button></td>
|
||||||
|
<td>Jane</td>
|
||||||
|
<td>21</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
So a table with a header, 2 columns and then a body and 2 rows. Now I want the button to remove the row.
|
||||||
|
First I will add it `hx-get="/demo1/delete"`. It mean *if the button is click, run this and use what it return*. What it return is ALWAYS an HTML.
|
||||||
|
Then `hx-swap="outerHTML"` it mean *replace the old HTML with the new one*.
|
||||||
|
And to finish `hx-target="closest tr"` it mean *do the change on this*. In this case the closest tr is the parent parent of the button, or the row the button is currently in.
|
||||||
|
With all change, we got:
|
||||||
|
```html
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Age</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="table_body">
|
||||||
|
<tr>
|
||||||
|
<td><button hx-target="closest tr" hx-get="/demo1/delete" hx-swap="outerHTML">Delete</button></td>
|
||||||
|
<td>John</td>
|
||||||
|
<td>20</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><button hx-target="closest tr" hx-get="/demo1/delete" hx-swap="outerHTML">Delete</button></td>
|
||||||
|
<td>Jane</td>
|
||||||
|
<td>21</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
No we just need to implement the route `/demo1/delete`. Well easy, it is supose to return the new HTML of a delete row... Meaning nothing, the funtion need to return nothing.
|
||||||
|
```python
|
||||||
|
@app.route('/demo1/delete', methods=['GET'])
|
||||||
|
def demo1_delete():
|
||||||
|
return ''
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! Ofc, at this point, you should do some server stuff. Meaning update the database ect. I don't do that here, I just show the interactivity, so if I reload the page, all changes are lost.
|
||||||
|
|
||||||
|
ANd the button work, if I click I instently remove it. It REACT! It's ALIVE and JSless!
|
||||||
|
Surely the form to add data isn't that simple...
|
||||||
|
```html
|
||||||
|
<form hx-post="/demo1/add" hx-target="#table_body" hx-swap="beforeend">
|
||||||
|
<input class="input" type="text" placeholder="Name" name="name" />
|
||||||
|
<input class="input" type="text" placeholder="Age" name="age" />
|
||||||
|
<input class="button is-primary" type="submit" value="Submit" />
|
||||||
|
</form>
|
||||||
|
```
|
||||||
|
|
||||||
|
Yes, that's it. If I click on Submit, I instantly get a new row!
|
||||||
|
|
||||||
|
Let's see what it do. So like before `hx-post` will issue a request to the server, a POST this time.
|
||||||
|
`hx-swap="beforeend"` mean *appends the content after the last child inside the target*. So after the last row of the table (can also be `afterbegin` to append at the beginning of the table)
|
||||||
|
`hx-target="#table_body"` is the `tbody` of the table above.
|
||||||
|
Let's see `/demo1/add`:
|
||||||
|
```python
|
||||||
|
@app.route('/demo1/add', methods=['POST'])
|
||||||
|
def demo1():
|
||||||
|
name = request.form['name']
|
||||||
|
age = request.form['age']
|
||||||
|
if name and age:
|
||||||
|
return f"<tr><td><button class='button is-danger is-small' hx-target='closest tr' hx-get='/demo1/delete' hx-swap='outerHTML'>Delete</button></td><td>{name}</td><td>{age}</td></tr>"
|
||||||
|
return ''
|
||||||
|
```
|
||||||
|
|
||||||
|
The function return the HTML of a new row. ANd because I say to add it after the last child of the `tbody`, it add a row to the table.
|
||||||
|
|
||||||
|
And that's it for this first demo/intro. We were able to do some crazy stuff in very few line. I highly recommand reading the HTMX doc in the future to check all availables features.
|
||||||
|
|
||||||
|
I was able to learn and do the app it few days while doing other stuff and managing issue on the dev virtual machine.
|
||||||
|
The shit was really impressive, and my boss is really similare to me in the way he don"t want to learn JS. He was really impressed.
|
||||||
|
|
||||||
|
So after that, I was convinced. Let's do JADE 2.0 using the Hyper PyFleX stack.
|
||||||
|
|
||||||
|
## Why GO ?
|
||||||
|
|
||||||
|
After doing a really simple chat where I can write and it answer in like 1-2h. I realease my python code is simple asf.
|
||||||
|
I also realise how I want my app to work and it's simple. remember simplicity boys!!!
|
||||||
|
|
||||||
|
So how it work. Every time I do something in the chat. I recreate it.
|
||||||
|
Let's take an example, I want to add a message to the chat:
|
||||||
|
User trigger -->
|
||||||
|
|
||||||
|
## Used teck
|
||||||
|
So here my full teck stack
|
||||||
|
- GO with Fiber
|
||||||
|
- HTMX
|
||||||
|
- MongoDB
|
||||||
|
|
||||||
|
## part 1 - The chat
|
||||||
|
|
||||||
|
Ok so as a first thing is a chat. What I need is:
|
||||||
|
- Text input at the bottom of the screen
|
||||||
|
- Avatar and message bubble
|
||||||
|
|
||||||
|
Should be easy right ? Hopefully with HTMX yes. Let's try...
|
||||||
|
|
||||||
|
# Conclusion
|
||||||
|
- Say a lot about JS during the article. JS is great, I guess, I didn't try. Every language have strenght weakness, blablabla. The point is that it shouln't be a necessity to learn it. And it shouldn't be so difficult to do basic stuff.
|
39
dockerfile
Normal file
39
dockerfile
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
############################
|
||||||
|
# STEP 1 build executable binary
|
||||||
|
############################
|
||||||
|
FROM golang:alpine AS builder
|
||||||
|
# Install git.
|
||||||
|
# Git is required for fetching the dependencies.
|
||||||
|
RUN apk update && apk add --no-cache 'git=~2'
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
ENV GO111MODULE=on
|
||||||
|
WORKDIR $GOPATH/src/packages/goginapp/
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Fetch dependencies.
|
||||||
|
# Using go get.
|
||||||
|
RUN go get -d -v
|
||||||
|
|
||||||
|
# Build the binary.
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /go/main .
|
||||||
|
|
||||||
|
############################
|
||||||
|
# STEP 2 build a small image
|
||||||
|
############################
|
||||||
|
FROM alpine:3
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
# Copy our static executable.
|
||||||
|
COPY --from=builder /go/main /go/main
|
||||||
|
COPY templates /go/templates
|
||||||
|
|
||||||
|
ENV PORT 8080
|
||||||
|
ENV GIN_MODE release
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
WORKDIR /go
|
||||||
|
|
||||||
|
# Run the Go Gin binary.
|
||||||
|
ENTRYPOINT ["/go/main"]
|
56
go.mod
Normal file
56
go.mod
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
module github.com/MrBounty/JADE2.0
|
||||||
|
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
|
github.com/bytedance/sonic v1.11.5 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.3 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/flosch/pongo2/v6 v6.0.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.4 // indirect
|
||||||
|
github.com/gofiber/template v1.8.3 // indirect
|
||||||
|
github.com/gofiber/template/django/v3 v3.1.11 // indirect
|
||||||
|
github.com/gofiber/utils v1.1.0 // indirect
|
||||||
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
|
github.com/google/uuid v1.5.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.0 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.1 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
|
go.mongodb.org/mongo-driver v1.15.0 // indirect
|
||||||
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
|
golang.org/x/net v0.24.0 // indirect
|
||||||
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
160
go.sum
Normal file
160
go.sum
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
|
github.com/bytedance/sonic v1.11.5 h1:G00FYjjqll5iQ1PYXynbg/hyzqBqavH8Mo9/oTopd9k=
|
||||||
|
github.com/bytedance/sonic v1.11.5/go.mod h1:X2PC2giUdj/Cv2lliWFLk6c/DUQok5rViJSemeB0wDw=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.0/go.mod h1:UmRT+IRTGKz/DAkzcEGzyVqQFJ7H9BqwBO3pm9H/+HY=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
|
github.com/cloudwego/base64x v0.1.3 h1:b5J/l8xolB7dyDTTmhJP2oTs5LdrjyrUFuNxdfq5hAg=
|
||||||
|
github.com/cloudwego/base64x v0.1.3/go.mod h1:1+1K5BUHIQzyapgpF7LwvOGAEDicKtt1umPV+aN8pi8=
|
||||||
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
|
||||||
|
github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||||
|
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||||
|
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
|
||||||
|
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
|
||||||
|
github.com/gofiber/template/django/v3 v3.1.11 h1:wE5k/wWNKGKxfeopaeB6IBijMiEVAxKHJVf1WMH5iNw=
|
||||||
|
github.com/gofiber/template/django/v3 v3.1.11/go.mod h1:sEUp0cr1iCuFx4GEtHEA7yRXgJmRdAVXwGMR3Q5JnyI=
|
||||||
|
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
|
||||||
|
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||||
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||||
|
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||||
|
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||||
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
|
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||||
|
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||||
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
|
||||||
|
go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||||
|
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
177
main.go
Normal file
177
main.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||||
|
"github.com/gofiber/template/django/v3"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mongoClient *mongo.Client
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id"`
|
||||||
|
Content string `bson:"message"`
|
||||||
|
Role string `bson:"role"`
|
||||||
|
Date time.Time `bson:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conversation struct {
|
||||||
|
ID string
|
||||||
|
Messages []Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectToMongoDB(uri string) {
|
||||||
|
serverAPI := options.ServerAPI(options.ServerAPIVersion1)
|
||||||
|
opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI)
|
||||||
|
client, err := mongo.Connect(context.TODO(), opts)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mongoClient = client
|
||||||
|
|
||||||
|
if err := mongoClient.Ping(context.TODO(), nil); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Import HTML using django engine/template
|
||||||
|
engine := django.New("./templates", ".html")
|
||||||
|
|
||||||
|
// Create new Fiber instance. Can use any framework. I use fiber for speed and simplicity
|
||||||
|
app := fiber.New(fiber.Config{
|
||||||
|
Views: engine,
|
||||||
|
AppName: "JADE 2.0",
|
||||||
|
EnablePrintRoutes: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
go connectToMongoDB("mongodb://localhost:27017")
|
||||||
|
|
||||||
|
// Add default logger
|
||||||
|
app.Use(logger.New())
|
||||||
|
|
||||||
|
// Add static files
|
||||||
|
app.Static("/", "./static")
|
||||||
|
|
||||||
|
// Add routes
|
||||||
|
app.Get("/", indexHandler)
|
||||||
|
app.Get("/isMongoDBConnected", isMongoDBConnectedHandler)
|
||||||
|
|
||||||
|
app.Get("/chat", chatPageHandler)
|
||||||
|
app.Post("/chat", addMessageHandler)
|
||||||
|
app.Delete("/chat", deleteMessageHandler)
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
app.Listen(":3000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexHandler(c *fiber.Ctx) error {
|
||||||
|
return c.Render("welcome_page", fiber.Map{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func chatPageHandler(c *fiber.Ctx) error {
|
||||||
|
return c.Render("chat/page", fiber.Map{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addMessageHandler(c *fiber.Ctx) error {
|
||||||
|
message := c.FormValue("message")
|
||||||
|
|
||||||
|
collection := mongoClient.Database("chat").Collection("messages")
|
||||||
|
collection.InsertOne(context.Background(), bson.M{"message": message, "role": "user", "date": time.Now()})
|
||||||
|
collection.InsertOne(context.Background(), bson.M{"message": "I did something!", "role": "bot", "date": time.Now()})
|
||||||
|
|
||||||
|
return generateChatHTML(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteMessageHandler(c *fiber.Ctx) error {
|
||||||
|
messageId := c.FormValue("messageId")
|
||||||
|
|
||||||
|
// Convert the string ID to a MongoDB ObjectID
|
||||||
|
objID, err := primitive.ObjectIDFromHex(messageId)
|
||||||
|
if err != nil {
|
||||||
|
// If the conversion fails, return an error response
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid message ID format",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete messages in the database. This one and all the following ones
|
||||||
|
collection := mongoClient.Database("chat").Collection("messages")
|
||||||
|
|
||||||
|
// Get the date of the message
|
||||||
|
var message Message
|
||||||
|
err = collection.FindOne(context.TODO(), bson.M{"_id": objID}).Decode(&message)
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
|
||||||
|
"error": "Message not found",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Failed to find message",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the message and all messages with a date after the specified date
|
||||||
|
filter := bson.M{
|
||||||
|
"$or": []bson.M{
|
||||||
|
{"_id": objID},
|
||||||
|
{"date": bson.M{"$gt": message.Date}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = collection.DeleteMany(context.TODO(), filter)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Failed to delete messages",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendString("") // TODO Main loog that return the all chat
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateChatHTML(c *fiber.Ctx) error {
|
||||||
|
// Get the messages from the database
|
||||||
|
collection := mongoClient.Database("chat").Collection("messages")
|
||||||
|
|
||||||
|
// Get all messages
|
||||||
|
cursor, err := collection.Find(context.TODO(), bson.D{})
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Failed to find messages",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the cursor to an array of messages
|
||||||
|
var messages []Message
|
||||||
|
if err = cursor.All(context.TODO(), &messages); err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Failed to convert cursor to array",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print messages
|
||||||
|
for _, message := range messages {
|
||||||
|
log.Println(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the HTML template with the messages
|
||||||
|
return c.Render("chat/messages", fiber.Map{
|
||||||
|
"messages": messages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMongoDBConnectedHandler(c *fiber.Ctx) error {
|
||||||
|
if mongoClient != nil {
|
||||||
|
return c.SendString("<h1>Connected</h1>")
|
||||||
|
}
|
||||||
|
return c.SendString("<h1 id='isMongoDBConnected' hx-get='/isMongoDBConnected' hx-trigger='every 1s' hx-swap='outerHTM'>Not connected</h1>")
|
||||||
|
}
|
BIN
static/bouvai.png
Normal file
BIN
static/bouvai.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 169 KiB |
25
templates/base.html
Normal file
25
templates/base.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>JADE 2.0</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.11"></script>
|
||||||
|
{% include 'navbar.html' %}
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-half">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
31
templates/chat/messages.html
Normal file
31
templates/chat/messages.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<div class="column" id="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
{% if message.Role == 'user' %}
|
||||||
|
<article class="message user-message" id='{{ message.id }}'>
|
||||||
|
<div class="message-header">
|
||||||
|
<p>User</p>
|
||||||
|
<form hx-delete="/chat" hx-swap="outerHTML" hx-target="#messages">
|
||||||
|
<input type="hidden" name="messageId" value='{{ message.id }}'>
|
||||||
|
<button class="delete" aria-label="delete"></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
{{ message.Content }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% else %}
|
||||||
|
<article class="message bot-message" id='{{ message.id }}'>
|
||||||
|
<div class="message-header">
|
||||||
|
<p>Bot</p>
|
||||||
|
<form hx-delete="/chat" hx-swap="outerHTML" hx-target="#messages">
|
||||||
|
<input type="hidden" name="messageId" value='{{ message.id }}'>
|
||||||
|
<button class="delete" aria-label="delete"></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
{{ message.Content }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
33
templates/chat/page.html
Normal file
33
templates/chat/page.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="columns" style="width: 100%;">
|
||||||
|
<div class="column" id="messages">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form hx-post="/chat" hx-target="#messages" hx-swap="outerHTML">
|
||||||
|
<input class="input" type="text" placeholder="Type your message here..." name="message" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
max-width: 90%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bot-message {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% endblock %}
|
19
templates/navbar.html
Normal file
19
templates/navbar.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<nav class="navbar is-primary">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="https://www.bouvai.com">
|
||||||
|
<img src="/bouvai.png">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-menu">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/chat">
|
||||||
|
Chat
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-end">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
11
templates/welcome_page.html
Normal file
11
templates/welcome_page.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
|
<h1>Welcome to JADE 2.0!</h1>
|
||||||
|
<h1 id="isMongoDBConnected" hx-get="/isMongoDBConnected" hx-trigger="load" hx-swap="outerHTML"></h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user