WIP
This commit is contained in:
parent
1c78cdfc68
commit
5750d13dc4
11
404.html
Normal file
11
404.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<h1> Project not found </h1>
|
||||
<p>Ensure the folder exists</p>
|
||||
</body>
|
||||
</html>
|
80
dnsmasq/dnsmasq.go
Normal file
80
dnsmasq/dnsmasq.go
Normal file
@ -0,0 +1,80 @@
|
||||
package dnsmasq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func Serve() {
|
||||
// Create a new DNS server
|
||||
server := &dns.Server{Addr: ":8989", Net: "udp"}
|
||||
|
||||
// Set up the handler for DNS requests
|
||||
dns.HandleFunc(".", handleRequest)
|
||||
|
||||
// Start listening for incoming DNS requests
|
||||
fmt.Println("Listening for DNS requests on port 8989...")
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
log.Fatalf("Failed to start DNS server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func forward(r *dns.Msg) (*dns.Msg, error) {
|
||||
// Forward the request to a real DNS server (for example, Google's public DNS server)
|
||||
client := new(dns.Client)
|
||||
realDNSAddr := "8.8.8.8:53" // Google's DNS server address
|
||||
|
||||
// Send the request to the actual DNS server
|
||||
response, _, err := client.Exchange(r, realDNSAddr)
|
||||
if err != nil {
|
||||
log.Printf("Failed to forward DNS request: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
domain := r.Question[0].Name
|
||||
domain = strings.Trim(domain, ".")
|
||||
parts := strings.Split(domain, ".")
|
||||
tld := parts[len(parts)-1]
|
||||
|
||||
response := dns.Msg{}
|
||||
response.SetReply(r)
|
||||
|
||||
// Check if it's a query for an A record (IPv4 address)
|
||||
if "test" == tld {
|
||||
log.Printf("Handling request for %s", domain)
|
||||
for _, question := range r.Question {
|
||||
if question.Qtype == dns.TypeA {
|
||||
// Create a loopback A record pointing to 127.0.0.1
|
||||
rr := &dns.A{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: question.Name,
|
||||
Rrtype: dns.TypeA,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 300, // TTL in seconds (5 minutes)
|
||||
},
|
||||
A: net.ParseIP("127.0.0.1"), // Loopback address
|
||||
}
|
||||
|
||||
response.Answer = append(response.Answer, rr)
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteMsg(&response)
|
||||
return
|
||||
}
|
||||
|
||||
// Send the response back to the original client
|
||||
resp, err := forward(r)
|
||||
if err != nil {
|
||||
panic("We broke boys")
|
||||
}
|
||||
w.WriteMsg(resp)
|
||||
}
|
49
fastcgi/constants.go
Normal file
49
fastcgi/constants.go
Normal file
@ -0,0 +1,49 @@
|
||||
package fastcgi
|
||||
|
||||
type FCGIRecordType uint8
|
||||
|
||||
const FCGI_LISTENSOCK_FILENO uint8 = 0
|
||||
const FCGI_HEADER_LEN uint8 = 8
|
||||
const VERSION_1 uint8 = 1
|
||||
const FCGI_NULL_REQUEST_ID uint8 = 0
|
||||
const FCGI_KEEP_CONN uint8 = 1
|
||||
const doubleCRLF = "\r\n\r\n"
|
||||
|
||||
const (
|
||||
FCGI_BEGIN_REQUEST FCGIRecordType = iota + 1
|
||||
FCGI_ABORT_REQUEST
|
||||
FCGI_END_REQUEST
|
||||
FCGI_PARAMS
|
||||
FCGI_STDIN
|
||||
FCGI_STDOUT
|
||||
FCGI_STDERR
|
||||
FCGI_DATA
|
||||
FCGI_GET_VALUES
|
||||
FCGI_GET_VALUES_RESULT
|
||||
FCGI_UNKNOWN_TYPE
|
||||
FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
|
||||
)
|
||||
|
||||
const (
|
||||
FCGI_RESPONDER uint8 = iota + 1
|
||||
FCGI_AUTHORIZER
|
||||
FCGI_FILTER
|
||||
)
|
||||
|
||||
const (
|
||||
FCGI_REQUEST_COMPLETE uint8 = iota
|
||||
FCGI_CANT_MPX_CONN
|
||||
FCGI_OVERLOADED
|
||||
FCGI_UNKNOWN_ROLE
|
||||
)
|
||||
|
||||
const (
|
||||
FCGI_MAX_CONNS string = "MAX_CONNS"
|
||||
FCGI_MAX_REQS string = "MAX_REQS"
|
||||
FCGI_MPXS_CONNS string = "MPXS_CONNS"
|
||||
)
|
||||
|
||||
const (
|
||||
maxWrite = 65500 // 65530 may work, but for compatibility
|
||||
maxPad = 255
|
||||
)
|
88
fastcgi/record.go
Normal file
88
fastcgi/record.go
Normal file
@ -0,0 +1,88 @@
|
||||
package fastcgi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// for padding so we don't have to allocate all the time
|
||||
// not synchronized because we don't care what the contents are
|
||||
var pad [maxPad]byte
|
||||
|
||||
// A record is essentially a "packet" in FastCGI.
|
||||
// The header lets the server know what type
|
||||
// of data is being sent, and it expects
|
||||
// a certain structure depending on
|
||||
// the type.
|
||||
type Header struct {
|
||||
Version uint8
|
||||
Type FCGIRecordType
|
||||
Id uint16
|
||||
ContentLength uint16
|
||||
PaddingLength uint8
|
||||
Reserved uint8
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Header Header
|
||||
Content []byte
|
||||
ReadBuffer []byte // Buffer to use when reading a response
|
||||
}
|
||||
|
||||
// Turn a record into a byte array so it can be
|
||||
// sent over the network. The byte array will
|
||||
// be in the shape/order that is expected
|
||||
// in the FastCGI protocol.
|
||||
func (r *Record) toBytes() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := binary.Write(&buf, binary.BigEndian, r.Header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := buf.Write(r.Content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := buf.Write(pad[:r.Header.PaddingLength]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (req *FCGIRequest) NewRecord(t FCGIRecordType, content []byte) *Record {
|
||||
r := Record{}
|
||||
|
||||
r.Header.Version = 1
|
||||
r.Header.Type = t
|
||||
r.Header.Id = req.Id
|
||||
r.Header.ContentLength = uint16(len(content))
|
||||
r.Header.PaddingLength = 0
|
||||
r.Content = content
|
||||
|
||||
return &r
|
||||
}
|
||||
|
||||
// FCGI_BEGIN_REQUEST record should
|
||||
// have a body of 8 bytes with:
|
||||
// - The first byte being the role
|
||||
// - The second byte being also the role
|
||||
// - The third byte being the flags
|
||||
// - The last five bytes are reserved for future use
|
||||
func (req *FCGIRequest) NewBeginRequestRecord() *Record {
|
||||
role := uint16(FCGI_RESPONDER)
|
||||
flags := byte(0)
|
||||
// Create an 8-byte array as per the FastCGI specification.
|
||||
var b [8]byte
|
||||
|
||||
// Split the 16-bit role into two bytes and assign them.
|
||||
b[0] = byte(role >> 8) // High byte
|
||||
b[1] = byte(role) // Low byte
|
||||
|
||||
// Set the flags.
|
||||
b[2] = flags
|
||||
|
||||
// The reserved bytes (b[3] to b[7]) will remain zero by default.
|
||||
|
||||
// Return a begin request record
|
||||
return req.NewRecord(FCGI_BEGIN_REQUEST, b[:])
|
||||
}
|
@ -23,12 +23,12 @@ func RequestFromHttp(r *http.Request) *FCGIRequest {
|
||||
c.Context = make(map[string]string)
|
||||
c.Context["SERVER_SOFTWARE"] = "oasis / fastcgi"
|
||||
c.Context["QUERY_STRING"] = r.URL.RawQuery
|
||||
c.Context["REMOTE_ADDR"] = "127.0.0.1"
|
||||
c.Context["REMOTE_ADDR"] = r.Host
|
||||
c.Context["REQUEST_METHOD"] = r.Method
|
||||
c.Context["REQUEST_URI"] = r.URL.Path
|
||||
c.Context["SERVER_ADDR"] = "localhost"
|
||||
c.Context["SERVER_PORT"] = "8000"
|
||||
c.Context["SERVER_NAME"] = "localhost"
|
||||
c.Context["SERVER_ADDR"] = r.Host
|
||||
c.Context["SERVER_PORT"] = "80"
|
||||
c.Context["SERVER_NAME"] = r.Host
|
||||
|
||||
// HTTP headers should be sent as FCGI_PARAMS.
|
||||
// We have to turn the name of the header
|
||||
|
9
go.mod
9
go.mod
@ -1,3 +1,12 @@
|
||||
module github.com/javif89/oasis
|
||||
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
)
|
||||
|
12
go.sum
12
go.sum
@ -0,0 +1,12 @@
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
32
main.go
32
main.go
@ -7,7 +7,9 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/javif89/oasis/dnsmasq"
|
||||
"github.com/javif89/oasis/fastcgi"
|
||||
)
|
||||
|
||||
@ -20,9 +22,30 @@ func fileExists(path string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func projectFolderExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Request received")
|
||||
root := "/home/javi/projects/javierfeliz.com/public/"
|
||||
|
||||
log.Printf("Host: %s", r.Host)
|
||||
folder := strings.ReplaceAll(r.Host, ".test", "")
|
||||
searchPath := "/home/javi/projects"
|
||||
|
||||
log.Printf("Checking %s for project", filepath.Join(searchPath, folder))
|
||||
|
||||
if !projectFolderExists(filepath.Join(searchPath, folder)) {
|
||||
http.ServeFile(w, r, "./404.html")
|
||||
return
|
||||
}
|
||||
|
||||
root := filepath.Join(searchPath, folder, "public")
|
||||
|
||||
// We will first try checking if the file exists
|
||||
// in case a static file is being requested
|
||||
@ -82,9 +105,12 @@ func main() {
|
||||
// Server
|
||||
http.HandleFunc("/", handleRequest)
|
||||
|
||||
fmt.Println("Starting server")
|
||||
fmt.Println("Starting DNSMasq Server")
|
||||
go dnsmasq.Serve()
|
||||
|
||||
err := http.ListenAndServe(":8000", nil)
|
||||
fmt.Println("Starting HTTP Server")
|
||||
|
||||
err := http.ListenAndServe(":80", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
263
resp.txt
Normal file
263
resp.txt
Normal file
@ -0,0 +1,263 @@
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Cache-Control: no-cache, private
|
||||
Date: Tue, 31 Dec 2024 07:44:10 GMT
|
||||
Set-Cookie: XSRF-TOKEN=eyJpdiI6InlVV1doeUhGS1lvZVJ6RTdNR3NLZ0E9PSIsInZhbHVlIjoiRmdmSUN2NHRYbkNBVlJodk4rcWgwY3VKd1BmQ2dIVGp4aFZPRm5xaEV0T0ZWd3Z6SXhXK1RneEZTYWEyUTlpY1JYTU5nWmVkRjhnYUtHbEtydGZ6ZTM2LytqMEZoL3Y5NTlGMFNpa1dSbzhuTTdYYXl1T2VYYmdHK3ZMOE5RZy8iLCJtYWMiOiI4YTA3NTY2NDM4NzkzODg2YmMzY2EwMzhlNGQ5YWU0NWI0Mjk1YjZlYjU1N2NkMjM0ZWIxNTVhZTViZTA4NDE1IiwidGFnIjoiIn0%3D; expires=Tue, 31 Dec 2024 09:44:10 GMT; Max-Age=7200; path=/; samesite=lax
|
||||
Set-Cookie: laravel_session=eyJpdiI6IkZybDVvc3pDWGZ5ZjF1STNVTi9NUEE9PSIsInZhbHVlIjoiM0lHVTN3SGtDVVdYQ3hIRXd6VlBNVDAzSE9MMnVzVFhPa2xmWloxUnRNdm82eXhjT0pBS1hqUGNWVlZTNDFZc0FjVTR6U05sdy9FVjBLWjNKMUxCVnN2RTNQYy9wWVZmRkg2STVaRkY2M0JLSThybml1ajlBZjlSdHRoS1EySkoiLCJtYWMiOiJhMzIzNDgzZTc2NjZjZTVjZDcwNGUzNDU2MjY3OTk2OGU5YmY1Y2UyNjgwMDZmMGFjMjA0MmZjZWY5NzIwMmU1IiwidGFnIjoiIn0%3D; expires=Tue, 31 Dec 2024 09:44:10 GMT; Max-Age=7200; path=/; httponly; samesite=lax
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="nxqEwaWyJmw0CQzS4yG2Xl7PDwm4DCTnuf2UzuWa">
|
||||
|
||||
<meta property="og:title" content="Home">
|
||||
<meta property="og:description" content="Tips and guides on anything Laravel, Go, PHP, and Software Architecture">
|
||||
<meta property="og:image" content="/img/ogimage.png">
|
||||
<meta property="og:url" content="http://javierfeliz.com.test">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<meta name="twitter:card" content="summary"></meta>
|
||||
|
||||
<title>Javier Feliz - Home</title>
|
||||
<meta name="description" content="Tips and guides on anything Laravel, Go, PHP, and Software Architecture">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="module" src="http://javierfeliz.com.test:5173/@vite/client" data-navigate-track="reload"></script><link rel="stylesheet" href="http://javierfeliz.com.test:5173/resources/css/app.css" data-navigate-track="reload" /><script type="module" src="http://javierfeliz.com.test:5173/resources/js/app.js" data-navigate-track="reload"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script defer src="https://kit.fontawesome.com/2751354a01.js" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body class="font-sans antialiased bg-gray-50">
|
||||
<div class="min-h-screen">
|
||||
<div class="w-full py-4">
|
||||
<nav class="max-w-6xl mx-auto flex flex-col lg:flex-row items-center gap-3 justify-between">
|
||||
<a href="http://javierfeliz.com.test" class="">
|
||||
<div class="flex gap-4 items-center justify-center">
|
||||
<div class="bg-gray-200 p-1 rounded-full inline-block">
|
||||
<img src="/img/logo.png" class="w-12 h-12 rounded-full" alt="">
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold">Javier Feliz</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex gap-3">
|
||||
<a href="https://github.com/javif89" target="_blank" class="text-md">
|
||||
<i class="fa-brands fa-github fa-xl"></i>
|
||||
</a>
|
||||
<a href="https://bsky.app/profile/javierfeliz.com" target="_blank" class="text-md">
|
||||
<i class="fa-brands fa-bluesky fa-xl"></i>
|
||||
</a>
|
||||
<a href="https://x.com/javierfelizweb" target="_blank" class="text-md">
|
||||
<i class="fa-brands fa-x-twitter fa-xl"></i>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row gap-2 w-full max-w-6xl mx-auto px-4 lg:px-4">
|
||||
<aside class="md:w-[300px] lg:pt-20 flex flex-row lg:block gap-5 items-cente justify-center">
|
||||
<a href="http://javierfeliz.com.test/blog" class="flex items-center gap-1 lg:gap-4 py-2 font-semibold hover:gap-6 transition-all">
|
||||
<img src="/img/nav-icons/blog.svg" alt="blog" class="w-8 h-8 block text-white">
|
||||
<p class="text-md lg:text-lg uppercase tracking-widest">Blog</p>
|
||||
</a>
|
||||
<div class="hidden lg:block">
|
||||
<div class="lg:pl-4 mb-2 hover:pl-5 hover:tracking-widest transition-all">
|
||||
<a href="http://javierfeliz.com.test/blog/laravel" class="">
|
||||
<h3 class="#6e19c5 text-lg">- Laravel</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div class="lg:pl-4 mb-2 hover:pl-5 hover:tracking-widest transition-all">
|
||||
<a href="http://javierfeliz.com.test/blog/go" class="">
|
||||
<h3 class="#ddf7c3 text-lg">- Go</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div class="lg:pl-4 mb-2 hover:pl-5 hover:tracking-widest transition-all">
|
||||
<a href="http://javierfeliz.com.test/blog/php" class="">
|
||||
<h3 class="#b419d9 text-lg">- PHP</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div class="lg:pl-4 mb-2 hover:pl-5 hover:tracking-widest transition-all">
|
||||
<a href="http://javierfeliz.com.test/blog/software-architecture" class="">
|
||||
<h3 class="#af0d05 text-lg">- Software Architecture</h3>
|
||||
</a>
|
||||
</div>
|
||||
<div class="lg:pl-4 mb-2 hover:pl-5 hover:tracking-widest transition-all">
|
||||
<a href="http://javierfeliz.com.test/blog/data-engineering" class="">
|
||||
<h3 class="#7c56d0 text-lg">- Data Engineering</h3>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="http://javierfeliz.com.test/tips" class="flex items-center gap-1 lg:gap-4 py-2 font-semibold hover:gap-6 transition-all">
|
||||
<img src="/img/nav-icons/tips.svg" alt="tips" class="w-8 h-8 block text-white">
|
||||
<p class="text-md lg:text-lg uppercase tracking-widest">Tips</p>
|
||||
</a>
|
||||
</aside>
|
||||
<main class="flex-1 py-10">
|
||||
<div class="prose lg:prose-xl prose-lg prose-slate"><p>I'm a software engineer with a passion for robust, elegant, and <strong>simple</strong> solutions.</p>
|
||||
<p>I write about
|
||||
<span class="font-bold text-[#ff2c20]">Laravel</span>,
|
||||
<span class="font-bold text-sky-400">Go</span>,
|
||||
<span class="font-bold text-indigo-700">PHP</span>,
|
||||
<span class="font-bold text-green-700">Data Engineering</span> and
|
||||
<span class="font-bold text-green-700">Software Architecture</span>, as well as my
|
||||
general thoughts on software development.</p>
|
||||
<p>I also make music as <a href="https://scrymusic.com">Scry</a> (solo project) and <a href="https://partipapi.com">Parti Papi</a> (duo w/ a friend).</p>
|
||||
<p>Thank you for coming to my little corner of the internet. I hope you like the content!</p>
|
||||
</div>
|
||||
<div class="mt-5 lg:mt-10">
|
||||
<a href="http://javierfeliz.com.test/blog/php/non-ipsum-est-repudiandae-odit-et-dolorem" class="block bg-white shadow-sm border mb-2">
|
||||
<article class="">
|
||||
<div class="px-4 py-2 lg:py-8">
|
||||
<div class="flex flex-col-reverse gap-1 lg:flex-row justify-between lg:items-center mb-2">
|
||||
<h2 class="text-lg lg:text-2xl font-bold">Non Ipsum Est Repudiandae Odit Et Dolorem.</h2>
|
||||
<time class="text-gray-300 font-bold flex-shrink-0">Dec 28 2024</time>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="font-bold text-lg" style="color: #b419d9">
|
||||
PHP
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="px-2 text-indigo-400 text-md">
|
||||
#Some tag
|
||||
</div>
|
||||
<div class="px-2 text-indigo-400 text-md">
|
||||
#Una tagita
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
<a href="http://javierfeliz.com.test/blog/php/quaerat-totam-sit-molestiae-rem" class="block bg-white shadow-sm border mb-2">
|
||||
<article class="">
|
||||
<div class="px-4 py-2 lg:py-8">
|
||||
<div class="flex flex-col-reverse gap-1 lg:flex-row justify-between lg:items-center mb-2">
|
||||
<h2 class="text-lg lg:text-2xl font-bold">Quaerat Totam Sit Molestiae Rem.</h2>
|
||||
<time class="text-gray-300 font-bold flex-shrink-0">Dec 28 2024</time>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="font-bold text-lg" style="color: #b419d9">
|
||||
PHP
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
<a href="http://javierfeliz.com.test/blog/laravel/dicta-consequatur-maxime-eaque-iste-unde" class="block bg-white shadow-sm border mb-2">
|
||||
<article class="">
|
||||
<div class="px-4 py-2 lg:py-8">
|
||||
<div class="flex flex-col-reverse gap-1 lg:flex-row justify-between lg:items-center mb-2">
|
||||
<h2 class="text-lg lg:text-2xl font-bold">Dicta Consequatur Maxime Eaque Iste Unde.</h2>
|
||||
<time class="text-gray-300 font-bold flex-shrink-0">Dec 28 2024</time>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="font-bold text-lg" style="color: #6e19c5">
|
||||
Laravel
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
<a href="http://javierfeliz.com.test/blog/data-engineering/minima-reprehenderit-dolor-beatae-vitae" class="block bg-white shadow-sm border mb-2">
|
||||
<article class="">
|
||||
<div class="px-4 py-2 lg:py-8">
|
||||
<div class="flex flex-col-reverse gap-1 lg:flex-row justify-between lg:items-center mb-2">
|
||||
<h2 class="text-lg lg:text-2xl font-bold">Minima Reprehenderit Dolor Beatae Vitae.</h2>
|
||||
<time class="text-gray-300 font-bold flex-shrink-0">Dec 28 2024</time>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="font-bold text-lg" style="color: #7c56d0">
|
||||
Data Engineering
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
<a href="http://javierfeliz.com.test/blog/go/provident-facilis-reiciendis-ipsam-est-ea-iusto" class="block bg-white shadow-sm border mb-2">
|
||||
<article class="">
|
||||
<div class="px-4 py-2 lg:py-8">
|
||||
<div class="flex flex-col-reverse gap-1 lg:flex-row justify-between lg:items-center mb-2">
|
||||
<h2 class="text-lg lg:text-2xl font-bold">Provident Facilis Reiciendis Ipsam Est Ea Iusto.</h2>
|
||||
<time class="text-gray-300 font-bold flex-shrink-0">Dec 28 2024</time>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="font-bold text-lg" style="color: #ddf7c3">
|
||||
Go
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
<a href="http://javierfeliz.com.test/blog/go/alias-accusamus-est-ea-architecto" class="block bg-white shadow-sm border mb-2">
|
||||
<article class="">
|
||||
<div class="px-4 py-2 lg:py-8">
|
||||
<div class="flex flex-col-reverse gap-1 lg:flex-row justify-between lg:items-center mb-2">
|
||||
<h2 class="text-lg lg:text-2xl font-bold">Alias Accusamus Est Ea Architecto.</h2>
|
||||
<time class="text-gray-300 font-bold flex-shrink-0">Dec 28 2024</time>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="font-bold text-lg" style="color: #ddf7c3">
|
||||
Go
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="mt-24">
|
||||
<div class="max-w-7xl mx-auto py-12 px-12 border shadow-sm lg:grid grid-cols-2 gap-4">
|
||||
<div class="mb-10 lg:mb-0">
|
||||
<h2 class="tracking-tight text-xl lg:text-2xl font-bold text-center mb-4">Want a monthly newsletter about Laravel, PHP, and software engineering?</h2>
|
||||
<p class="text-lg text-gray-500">
|
||||
I like to cut to the chase and find the fastest and most effective way to build products and web applications. If you like that idea,
|
||||
then you will love my newsletter. Every month I'll send a summary of blog posts and quick tips/snippets that will help you develop
|
||||
your ideas faster.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-center pl-10">
|
||||
<form action="https://javier-feliz.mailcoach.app/subscribe/9922dbee-0a6a-4c39-99dd-8085f624503f" method="POST"
|
||||
class="flex flex-1 gap-3">
|
||||
<input type="email" name="email" placeholder="Your email address" class="flex-1 rounded-md bg-transparent" />
|
||||
<div style="position: absolute; left: -9999px">
|
||||
<label for="website-honeypot">Your honeypot</label>
|
||||
<input type="text" id="website-honeypot" name="honeypot" tabindex="-1" autocomplete="nope" />
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="bg-gray-900 text-white uppercase tracking-wide px-4 py-2 rounded-md">Subscribe</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-24 text-center">
|
||||
<p class="text-md text-gray-300 mb-4">Javier Feliz 2024</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user