Compare commits
No commits in common. "main" and "old" have entirely different histories.
56 changed files with 3063 additions and 2215 deletions
3
.eslintrc.json
Normal file
3
.eslintrc.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
41
.gitignore
vendored
41
.gitignore
vendored
|
|
@ -1,9 +1,38 @@
|
||||||
.DS_STORE
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
.env
|
# dependencies
|
||||||
**/*_templ.go
|
/node_modules
|
||||||
**/*_templ.txt
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
**/configmap.yaml
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
tailwindcss
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
|
||||||
18
README.md
18
README.md
|
|
@ -1,13 +1,11 @@
|
||||||
# fastbin: a text sharing application
|
This is a code sharing web application written in next.js using Serverless Functions and MongoDB/FaunaDB as database.
|
||||||
|
|
||||||
https://fastbin.lab.divyam.dev
|
## Getting Started
|
||||||
|
|
||||||
A text sharing application made with microservice architecture in golang.
|
First, run the development server:
|
||||||
Services:
|
|
||||||
- API Server (Read and Write)
|
|
||||||
- Custom Key Generator Service
|
|
||||||
- Database Service
|
|
||||||
|
|
||||||
To-do:
|
```bash
|
||||||
- Add data deletion service which removes data after certain time.
|
npm run dev
|
||||||
- Make key generator service faster and scalable with bloom filter and redis caching.
|
# or
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.35.1
|
|
||||||
// protoc v5.28.2
|
|
||||||
// source: keygen.proto
|
|
||||||
|
|
||||||
package keygen
|
|
||||||
|
|
||||||
import (
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Empty struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Empty) Reset() {
|
|
||||||
*x = Empty{}
|
|
||||||
mi := &file_keygen_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Empty) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Empty) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Empty) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_keygen_proto_msgTypes[0]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Empty) Descriptor() ([]byte, []int) {
|
|
||||||
return file_keygen_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Key struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Key) Reset() {
|
|
||||||
*x = Key{}
|
|
||||||
mi := &file_keygen_proto_msgTypes[1]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Key) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Key) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Key) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_keygen_proto_msgTypes[1]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Key.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Key) Descriptor() ([]byte, []int) {
|
|
||||||
return file_keygen_proto_rawDescGZIP(), []int{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Key) GetValue() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Value
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_keygen_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
var file_keygen_proto_rawDesc = []byte{
|
|
||||||
0x0a, 0x0c, 0x6b, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
|
|
||||||
0x6b, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,
|
|
||||||
0x1b, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
|
|
||||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x35, 0x0a, 0x06,
|
|
||||||
0x4b, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x12, 0x2b, 0x0a, 0x0b, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61,
|
|
||||||
0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x0d, 0x2e, 0x6b, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x2e, 0x45,
|
|
||||||
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x6b, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x2e, 0x4b, 0x65,
|
|
||||||
0x79, 0x22, 0x00, 0x42, 0x19, 0x5a, 0x17, 0x66, 0x61, 0x73, 0x74, 0x62, 0x69, 0x6e, 0x2f, 0x69,
|
|
||||||
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6b, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x62, 0x06,
|
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_keygen_proto_rawDescOnce sync.Once
|
|
||||||
file_keygen_proto_rawDescData = file_keygen_proto_rawDesc
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_keygen_proto_rawDescGZIP() []byte {
|
|
||||||
file_keygen_proto_rawDescOnce.Do(func() {
|
|
||||||
file_keygen_proto_rawDescData = protoimpl.X.CompressGZIP(file_keygen_proto_rawDescData)
|
|
||||||
})
|
|
||||||
return file_keygen_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_keygen_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
|
||||||
var file_keygen_proto_goTypes = []any{
|
|
||||||
(*Empty)(nil), // 0: keygen.Empty
|
|
||||||
(*Key)(nil), // 1: keygen.Key
|
|
||||||
}
|
|
||||||
var file_keygen_proto_depIdxs = []int32{
|
|
||||||
0, // 0: keygen.Keygen.GenerateKey:input_type -> keygen.Empty
|
|
||||||
1, // 1: keygen.Keygen.GenerateKey:output_type -> keygen.Key
|
|
||||||
1, // [1:2] is the sub-list for method output_type
|
|
||||||
0, // [0:1] is the sub-list for method input_type
|
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
|
||||||
0, // [0:0] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_keygen_proto_init() }
|
|
||||||
func file_keygen_proto_init() {
|
|
||||||
if File_keygen_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: file_keygen_proto_rawDesc,
|
|
||||||
NumEnums: 0,
|
|
||||||
NumMessages: 2,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 1,
|
|
||||||
},
|
|
||||||
GoTypes: file_keygen_proto_goTypes,
|
|
||||||
DependencyIndexes: file_keygen_proto_depIdxs,
|
|
||||||
MessageInfos: file_keygen_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_keygen_proto = out.File
|
|
||||||
file_keygen_proto_rawDesc = nil
|
|
||||||
file_keygen_proto_goTypes = nil
|
|
||||||
file_keygen_proto_depIdxs = nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
option go_package = "fastbin/internal/keygen";
|
|
||||||
|
|
||||||
package keygen;
|
|
||||||
|
|
||||||
service Keygen {
|
|
||||||
rpc GenerateKey(Empty) returns (Key) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Empty {}
|
|
||||||
|
|
||||||
message Key {
|
|
||||||
string value = 1;
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
|
||||||
// - protoc v5.28.2
|
|
||||||
// source: keygen.proto
|
|
||||||
|
|
||||||
package keygen
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
grpc "google.golang.org/grpc"
|
|
||||||
codes "google.golang.org/grpc/codes"
|
|
||||||
status "google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the grpc package it is being compiled against.
|
|
||||||
// Requires gRPC-Go v1.64.0 or later.
|
|
||||||
const _ = grpc.SupportPackageIsVersion9
|
|
||||||
|
|
||||||
const (
|
|
||||||
Keygen_GenerateKey_FullMethodName = "/keygen.Keygen/GenerateKey"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeygenClient is the client API for Keygen service.
|
|
||||||
//
|
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
|
||||||
type KeygenClient interface {
|
|
||||||
GenerateKey(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Key, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type keygenClient struct {
|
|
||||||
cc grpc.ClientConnInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKeygenClient(cc grpc.ClientConnInterface) KeygenClient {
|
|
||||||
return &keygenClient{cc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *keygenClient) GenerateKey(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Key, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(Key)
|
|
||||||
err := c.cc.Invoke(ctx, Keygen_GenerateKey_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeygenServer is the server API for Keygen service.
|
|
||||||
// All implementations must embed UnimplementedKeygenServer
|
|
||||||
// for forward compatibility.
|
|
||||||
type KeygenServer interface {
|
|
||||||
GenerateKey(context.Context, *Empty) (*Key, error)
|
|
||||||
mustEmbedUnimplementedKeygenServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnimplementedKeygenServer must be embedded to have
|
|
||||||
// forward compatible implementations.
|
|
||||||
//
|
|
||||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
|
||||||
// pointer dereference when methods are called.
|
|
||||||
type UnimplementedKeygenServer struct{}
|
|
||||||
|
|
||||||
func (UnimplementedKeygenServer) GenerateKey(context.Context, *Empty) (*Key, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GenerateKey not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedKeygenServer) mustEmbedUnimplementedKeygenServer() {}
|
|
||||||
func (UnimplementedKeygenServer) testEmbeddedByValue() {}
|
|
||||||
|
|
||||||
// UnsafeKeygenServer may be embedded to opt out of forward compatibility for this service.
|
|
||||||
// Use of this interface is not recommended, as added methods to KeygenServer will
|
|
||||||
// result in compilation errors.
|
|
||||||
type UnsafeKeygenServer interface {
|
|
||||||
mustEmbedUnimplementedKeygenServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterKeygenServer(s grpc.ServiceRegistrar, srv KeygenServer) {
|
|
||||||
// If the following call pancis, it indicates UnimplementedKeygenServer was
|
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
|
||||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
|
||||||
t.testEmbeddedByValue()
|
|
||||||
}
|
|
||||||
s.RegisterService(&Keygen_ServiceDesc, srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Keygen_GenerateKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Empty)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(KeygenServer).GenerateKey(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Keygen_GenerateKey_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(KeygenServer).GenerateKey(ctx, req.(*Empty))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keygen_ServiceDesc is the grpc.ServiceDesc for Keygen service.
|
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
|
||||||
// and not to be introspected or modified (even as a copy)
|
|
||||||
var Keygen_ServiceDesc = grpc.ServiceDesc{
|
|
||||||
ServiceName: "keygen.Keygen",
|
|
||||||
HandlerType: (*KeygenServer)(nil),
|
|
||||||
Methods: []grpc.MethodDesc{
|
|
||||||
{
|
|
||||||
MethodName: "GenerateKey",
|
|
||||||
Handler: _Keygen_GenerateKey_Handler,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Streams: []grpc.StreamDesc{},
|
|
||||||
Metadata: "keygen.proto",
|
|
||||||
}
|
|
||||||
BIN
bin/web
BIN
bin/web
Binary file not shown.
|
|
@ -1,22 +0,0 @@
|
||||||
FROM golang:1.23-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN go build -o api-server ./cmd/api-server
|
|
||||||
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY --from=builder /app/api-server /app/api-server
|
|
||||||
|
|
||||||
ENV GIN_MODE=release
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/api-server"]
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
services:
|
|
||||||
psql_bp:
|
|
||||||
image: postgres:latest
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${DB_DATABASE}
|
|
||||||
POSTGRES_USER: ${DB_USERNAME}
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U", $DB_USERNAME, "-d", $DB_DATABASE]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
- psql_volume_bp:/var/lib/postgresql/data
|
|
||||||
api_server:
|
|
||||||
env_file:
|
|
||||||
- ../../.env
|
|
||||||
depends_on:
|
|
||||||
psql_bp:
|
|
||||||
condition: service_healthy
|
|
||||||
keygen:
|
|
||||||
condition: service_started
|
|
||||||
build:
|
|
||||||
context: ../../
|
|
||||||
dockerfile: build/docker/api-server/Dockerfile
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
keygen:
|
|
||||||
build:
|
|
||||||
context: ../../
|
|
||||||
dockerfile: build/docker/keygen/Dockerfile
|
|
||||||
web_server:
|
|
||||||
env_file:
|
|
||||||
- ../../.env
|
|
||||||
build:
|
|
||||||
context: ../../
|
|
||||||
dockerfile: build/docker/api-server/Dockerfile
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
psql_volume_bp:
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
FROM golang:1.23-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN go build -o keygen ./cmd/keygen
|
|
||||||
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY --from=builder /app/keygen /app/keygen
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/keygen"]
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
FROM golang:1.23-alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN apk --no-cache add curl nodejs npm
|
|
||||||
|
|
||||||
RUN npm install -D tailwindcss@3
|
|
||||||
|
|
||||||
RUN go install github.com/a-h/templ/cmd/templ@latest
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN go get github.com/a-h/templ@latest
|
|
||||||
RUN go mod tidy
|
|
||||||
|
|
||||||
RUN templ generate
|
|
||||||
|
|
||||||
|
|
||||||
RUN npx tailwindcss -i ./internal/web/assets/css/input.css -o ./internal/web/assets/css/output.css
|
|
||||||
|
|
||||||
RUN go build -o web ./cmd/web
|
|
||||||
|
|
||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY --from=builder /app/web /app/web
|
|
||||||
|
|
||||||
ENV GIN_MODE=release
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/web"]
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: api-server
|
|
||||||
spec:
|
|
||||||
replicas: 3
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: api-server
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: api-server
|
|
||||||
spec:
|
|
||||||
initContainers:
|
|
||||||
- name: wait-for-psql
|
|
||||||
image: busybox
|
|
||||||
command: ['sh', '-c', 'until nc -z psql-service 5432; do echo waiting for postgres readiness; sleep 2; done;']
|
|
||||||
containers:
|
|
||||||
- name: api-server
|
|
||||||
image: registry.lab.divyam.dev/fastbin-api-server:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
env:
|
|
||||||
- name: DB_DATABASE
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: DB_DATABASE
|
|
||||||
- name: DB_USERNAME
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: DB_USERNAME
|
|
||||||
- name: DB_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: DB_PASSWORD
|
|
||||||
- name: DB_HOST
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: DB_HOST
|
|
||||||
- name: DB_PORT
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: DB_PORT
|
|
||||||
- name: KEYGEN_HOST
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: KEYGEN_HOST
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: api-server-service
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
selector:
|
|
||||||
app: api-server
|
|
||||||
type: LoadBalancer
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: fastbin-config
|
|
||||||
data:
|
|
||||||
DB_DATABASE: "fastbin"
|
|
||||||
DB_USERNAME: "username"
|
|
||||||
DB_PASSWORD: "password"
|
|
||||||
DB_HOST: "psql-service"
|
|
||||||
DB_PORT: "5432"
|
|
||||||
KEYGEN_HOST: "keygen-service"
|
|
||||||
API_URL: "http://api-server-service"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: keygen
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: keygen
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: keygen
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: keygen
|
|
||||||
image: registry.lab.divyam.dev/fastbin-keygen-service:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: keygen-service
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 8080
|
|
||||||
targetPort: 8080
|
|
||||||
selector:
|
|
||||||
app: keygen
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolume
|
|
||||||
metadata:
|
|
||||||
name: psql-pv
|
|
||||||
spec:
|
|
||||||
capacity:
|
|
||||||
storage: 5Gi
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
hostPath:
|
|
||||||
path: /mnt/data/postgres
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: psql-volume-claim
|
|
||||||
labels:
|
|
||||||
type: local
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 2Gi
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: psql-bp
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: psql-bp
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: psql-bp
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: postgres
|
|
||||||
image: postgres:latest
|
|
||||||
env:
|
|
||||||
- name: POSTGRES_DB
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: DB_DATABASE
|
|
||||||
- name: POSTGRES_USER
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: DB_USERNAME
|
|
||||||
- name: POSTGRES_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: DB_PASSWORD
|
|
||||||
- name: PGDATA
|
|
||||||
value: /var/lib/postgresql/data/pgdata
|
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /var/lib/postgresql/data
|
|
||||||
name: psql-storage
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- sh
|
|
||||||
- "-c"
|
|
||||||
- "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 5
|
|
||||||
timeoutSeconds: 5
|
|
||||||
failureThreshold: 5
|
|
||||||
volumes:
|
|
||||||
- name: psql-storage
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: psql-volume-claim
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: psql-service
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 5432
|
|
||||||
targetPort: 5432
|
|
||||||
selector:
|
|
||||||
app: psql-bp
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: fastbin-web
|
|
||||||
spec:
|
|
||||||
replicas: 3
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: fastbin-web
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: fastbin-web
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: api-server
|
|
||||||
image: registry.lab.divyam.dev/fastbin-web:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
env:
|
|
||||||
- name: API_URL
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: fastbin-config
|
|
||||||
key: API_URL
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: fastbin-web-service
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
selector:
|
|
||||||
app: fastbin-web
|
|
||||||
type: LoadBalancer
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
apiserver "fastbin/internal/api-server"
|
|
||||||
"fastbin/internal/pkg/env"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
port, err := strconv.Atoi(env.GetEnv("API_INTERNAL_PORT", "8080"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error listening port: %v, err: %v", port, err)
|
|
||||||
}
|
|
||||||
server := apiserver.NewAPIServer(port)
|
|
||||||
err = server.ListenAndServe()
|
|
||||||
if err != nil && err != http.ErrServerClosed {
|
|
||||||
panic(fmt.Sprintf("http server error: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
keygen "fastbin/internal/keygen"
|
|
||||||
"fastbin/internal/pkg/env"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
port, err := strconv.Atoi(env.GetEnv("KEYGEN_INTERNAL_PORT", "8080"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error listening port: %v, err: %v", port, err)
|
|
||||||
}
|
|
||||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to listen: %v", err)
|
|
||||||
}
|
|
||||||
err = keygen.NewKeygenServer().Serve(lis)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("grpc server error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fastbin/internal/pkg/env"
|
|
||||||
webserver "fastbin/internal/web"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
port, err := strconv.Atoi(env.GetEnv("WEB_INTERNAL_PORT", "8080"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error listening port: %v, err: %v", port, err)
|
|
||||||
}
|
|
||||||
server := webserver.NewServer(port)
|
|
||||||
err = server.ListenAndServe()
|
|
||||||
if err != nil && err != http.ErrServerClosed {
|
|
||||||
panic(fmt.Sprintf("http server error: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51
go.mod
51
go.mod
|
|
@ -1,51 +0,0 @@
|
||||||
module fastbin
|
|
||||||
|
|
||||||
go 1.23.2
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/a-h/templ v0.2.793
|
|
||||||
github.com/gin-gonic/gin v1.10.0
|
|
||||||
github.com/joho/godotenv v1.5.1
|
|
||||||
google.golang.org/grpc v1.67.1
|
|
||||||
google.golang.org/protobuf v1.34.2
|
|
||||||
gorm.io/driver/postgres v1.5.9
|
|
||||||
gorm.io/gorm v1.25.12
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // 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.20.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
|
||||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
|
||||||
github.com/rogpeppe/go-internal v1.6.1 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
|
||||||
golang.org/x/crypto v0.28.0 // indirect
|
|
||||||
golang.org/x/net v0.28.0 // indirect
|
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
|
||||||
golang.org/x/text v0.19.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
||||||
126
go.sum
126
go.sum
|
|
@ -1,126 +0,0 @@
|
||||||
github.com/a-h/templ v0.2.793 h1:Io+/ocnfGWYO4VHdR0zBbf39PQlnzVCVVD+wEEs6/qY=
|
|
||||||
github.com/a-h/templ v0.2.793/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
|
|
||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
|
||||||
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/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
|
||||||
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
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.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
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.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
|
||||||
github.com/go-playground/validator/v10 v10.20.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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
|
||||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
||||||
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/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
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-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/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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
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=
|
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
|
||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
|
||||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
|
||||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
|
||||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
|
||||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
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=
|
|
||||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
|
||||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
|
||||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
package apiserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
pb "fastbin/api/keygen"
|
|
||||||
|
|
||||||
"fastbin/internal/pkg/env"
|
|
||||||
paste "fastbin/internal/pkg/paste"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Paste = paste.Paste
|
|
||||||
|
|
||||||
type APIServer struct {
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
database = os.Getenv("DB_DATABASE")
|
|
||||||
password = os.Getenv("DB_PASSWORD")
|
|
||||||
username = os.Getenv("DB_USERNAME")
|
|
||||||
dbport = os.Getenv("DB_PORT")
|
|
||||||
host = os.Getenv("DB_HOST")
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewAPIServer(port int) *http.Server {
|
|
||||||
dbString := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", host, username, password, database, dbport)
|
|
||||||
db, err := gorm.Open(postgres.Open(dbString), &gorm.Config{})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to connect to db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db.AutoMigrate(&Paste{})
|
|
||||||
|
|
||||||
s := APIServer{db: db}
|
|
||||||
|
|
||||||
r := gin.Default()
|
|
||||||
r.POST("/write", s.write)
|
|
||||||
r.GET("/read/:key", s.read)
|
|
||||||
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%d", port),
|
|
||||||
Handler: r,
|
|
||||||
}
|
|
||||||
|
|
||||||
return server
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as *APIServer) read(gc *gin.Context) {
|
|
||||||
key, _ := gc.Params.Get("key")
|
|
||||||
|
|
||||||
var paste Paste
|
|
||||||
res := as.db.First(&paste, "id = ?", key)
|
|
||||||
if res.Error != nil {
|
|
||||||
gc.JSON(http.StatusNotFound, gin.H{
|
|
||||||
"error": "Not Found.",
|
|
||||||
"text": "",
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
gc.JSON(http.StatusOK, gin.H{
|
|
||||||
"text": paste.Text,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type WriteRequestBody struct {
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as *APIServer) write(gc *gin.Context) {
|
|
||||||
var requestBody WriteRequestBody
|
|
||||||
if err := gc.BindJSON(&requestBody); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
gc.JSON(http.StatusBadRequest, gin.H{
|
|
||||||
"key": "",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := as.try_write(requestBody.Text)
|
|
||||||
for tries := 0; err != nil && tries < 5; tries++ {
|
|
||||||
key, err = as.try_write(requestBody.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
gc.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"key": "",
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
gc.JSON(http.StatusOK, gin.H{
|
|
||||||
"key": key,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (as *APIServer) try_write(data string) (string, error) {
|
|
||||||
grpcServerURL := env.GetEnv("KEYGEN_HOST", "localhost") + ":" + env.GetEnv("KEYGEN_PORT", "8080")
|
|
||||||
conn, err := grpc.NewClient(grpcServerURL, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
c := pb.NewKeygenClient(conn)
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
||||||
r, err := c.GenerateKey(ctx, &pb.Empty{})
|
|
||||||
cancel()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := r.Value
|
|
||||||
res := as.db.Create(&Paste{ID: key, Text: data})
|
|
||||||
if res.Error != nil {
|
|
||||||
return "", res.Error
|
|
||||||
}
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package keygen
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
"math/rand"
|
|
||||||
|
|
||||||
pb "fastbin/api/keygen"
|
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type keygenServer struct {
|
|
||||||
pb.UnimplementedKeygenServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *keygenServer) GenerateKey(ctx context.Context, req *pb.Empty) (*pb.Key, error) {
|
|
||||||
b := make([]byte, 6)
|
|
||||||
for i := range b {
|
|
||||||
b[i] = 'a' + byte(rand.Intn(26))
|
|
||||||
}
|
|
||||||
|
|
||||||
key := pb.Key{Value: string(b)}
|
|
||||||
return &key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKeygenServer() *grpc.Server {
|
|
||||||
grpcSever := grpc.NewServer()
|
|
||||||
pb.RegisterKeygenServer(grpcSever, &keygenServer{})
|
|
||||||
return grpcSever
|
|
||||||
}
|
|
||||||
28
internal/pkg/env/env.go
vendored
28
internal/pkg/env/env.go
vendored
|
|
@ -1,28 +0,0 @@
|
||||||
package env
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
env := os.Getenv("FASTBIN_ENV")
|
|
||||||
if env == "" {
|
|
||||||
env = "development"
|
|
||||||
}
|
|
||||||
|
|
||||||
godotenv.Load(".env." + env + ".local")
|
|
||||||
if env != "test" {
|
|
||||||
godotenv.Load(".env.local")
|
|
||||||
}
|
|
||||||
godotenv.Load(".env." + env)
|
|
||||||
godotenv.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEnv(key, fallback string) string {
|
|
||||||
if value, ok := os.LookupEnv(key); ok {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package paste
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Paste struct {
|
|
||||||
gorm.Model
|
|
||||||
ID string `gorm:"primaryKey"`
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
:root {
|
|
||||||
--primary-color: 29 29 29;
|
|
||||||
--border-color: 69 69 69;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,740 +0,0 @@
|
||||||
*, ::before, ::after {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
::backdrop {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
|
||||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
|
||||||
*/
|
|
||||||
|
|
||||||
*,
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
/* 1 */
|
|
||||||
border-width: 0;
|
|
||||||
/* 2 */
|
|
||||||
border-style: solid;
|
|
||||||
/* 2 */
|
|
||||||
border-color: #e5e7eb;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
--tw-content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use a consistent sensible line-height in all browsers.
|
|
||||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
||||||
3. Use a more readable tab size.
|
|
||||||
4. Use the user's configured `sans` font-family by default.
|
|
||||||
5. Use the user's configured `sans` font-feature-settings by default.
|
|
||||||
6. Use the user's configured `sans` font-variation-settings by default.
|
|
||||||
7. Disable tap highlights on iOS
|
|
||||||
*/
|
|
||||||
|
|
||||||
html,
|
|
||||||
:host {
|
|
||||||
line-height: 1.5;
|
|
||||||
/* 1 */
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
/* 2 */
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
-o-tab-size: 4;
|
|
||||||
tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
/* 4 */
|
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 5 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 6 */
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
/* 7 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove the margin in all browsers.
|
|
||||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Add the correct height in Firefox.
|
|
||||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
|
||||||
3. Ensure horizontal rules are visible by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
hr {
|
|
||||||
height: 0;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-top-width: 1px;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abbr:where([title]) {
|
|
||||||
-webkit-text-decoration: underline dotted;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the default font size and weight for headings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-size: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset links to optimize for opt-in styling instead of opt-out.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font weight in Edge and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use the user's configured `mono` font-family by default.
|
|
||||||
2. Use the user's configured `mono` font-feature-settings by default.
|
|
||||||
3. Use the user's configured `mono` font-variation-settings by default.
|
|
||||||
4. Correct the odd `em` font sizing in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp,
|
|
||||||
pre {
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
/* 1 */
|
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 2 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 3 */
|
|
||||||
font-size: 1em;
|
|
||||||
/* 4 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font size in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
|
||||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
|
||||||
3. Remove gaps between table borders by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
table {
|
|
||||||
text-indent: 0;
|
|
||||||
/* 1 */
|
|
||||||
border-color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-collapse: collapse;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Change the font styles in all browsers.
|
|
||||||
2. Remove the margin in Firefox and Safari.
|
|
||||||
3. Remove default padding in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-feature-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-variation-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-size: 100%;
|
|
||||||
/* 1 */
|
|
||||||
font-weight: inherit;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 1 */
|
|
||||||
letter-spacing: inherit;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 1 */
|
|
||||||
margin: 0;
|
|
||||||
/* 2 */
|
|
||||||
padding: 0;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inheritance of text transform in Edge and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Remove default button styles.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input:where([type='button']),
|
|
||||||
input:where([type='reset']),
|
|
||||||
input:where([type='submit']) {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
background-color: transparent;
|
|
||||||
/* 2 */
|
|
||||||
background-image: none;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Use the modern Firefox focus style for all focusable elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-focusring {
|
|
||||||
outline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-ui-invalid {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct vertical alignment in Chrome and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Correct the cursor style of increment and decrement buttons in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-inner-spin-button,
|
|
||||||
::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the odd appearance in Chrome and Safari.
|
|
||||||
2. Correct the outline style in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type='search'] {
|
|
||||||
-webkit-appearance: textfield;
|
|
||||||
/* 1 */
|
|
||||||
outline-offset: -2px;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inner padding in Chrome and Safari on macOS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Change font properties to `inherit` in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
font: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct display in Chrome and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Removes the default spacing and border for appropriate elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
blockquote,
|
|
||||||
dl,
|
|
||||||
dd,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
hr,
|
|
||||||
figure,
|
|
||||||
p,
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
menu {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset default styling for dialogs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
dialog {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent resizing textareas horizontally by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
|
||||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input::-moz-placeholder, textarea::-moz-placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
input::placeholder,
|
|
||||||
textarea::placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the default cursor for buttons.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
[role="button"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Make sure disabled buttons don't get the pointer cursor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
|
||||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
video,
|
|
||||||
canvas,
|
|
||||||
audio,
|
|
||||||
iframe,
|
|
||||||
embed,
|
|
||||||
object {
|
|
||||||
display: block;
|
|
||||||
/* 1 */
|
|
||||||
vertical-align: middle;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
|
||||||
|
|
||||||
[hidden]:where(:not([hidden="until-found"])) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--primary-color: 29 29 29;
|
|
||||||
--border-color: 69 69 69;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pointer-events-none {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-block {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-\[70px\] {
|
|
||||||
height: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-dvh {
|
|
||||||
height: 100dvh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-full {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-\[70px\] {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-dvw {
|
|
||||||
width: 100dvw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-1 {
|
|
||||||
flex: 1 1 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cursor-pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-none {
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resize-none {
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resize {
|
|
||||||
resize: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-col {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-start {
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-stretch {
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-center {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-between {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.self-stretch {
|
|
||||||
align-self: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-b-2 {
|
|
||||||
border-bottom-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-none {
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-b-border {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-bottom-color: rgb(var(--border-color) / var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-primary {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(var(--primary-color) / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-transparent {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-2\.5 {
|
|
||||||
padding-left: 0.625rem;
|
|
||||||
padding-right: 0.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-\[20px\] {
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-\[10px\] {
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-middle {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-4xl {
|
|
||||||
font-size: 2.25rem;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-8xl {
|
|
||||||
font-size: 6rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-xl {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
line-height: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.italic {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-white {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-none {
|
|
||||||
outline: 2px solid transparent;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter {
|
|
||||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.transition {
|
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
|
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
|
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
transition-duration: 150ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:bg-border\/50:hover {
|
|
||||||
background-color: rgb(var(--border-color) / 0.5);
|
|
||||||
}
|
|
||||||
1
internal/web/assets/js/htmx.min.js
vendored
1
internal/web/assets/js/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,15 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed assets
|
|
||||||
var f embed.FS
|
|
||||||
|
|
||||||
var Files fs.FS
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Files, _ = fs.Sub(f, "assets")
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// Credits: https://github.com/a-h/templ/blob/main/examples/integration-gin/gintemplrenderer/renderer.go
|
|
||||||
|
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/render"
|
|
||||||
|
|
||||||
"github.com/a-h/templ"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Default = &HTMLTemplRenderer{}
|
|
||||||
|
|
||||||
type HTMLTemplRenderer struct {
|
|
||||||
FallbackHtmlRenderer render.HTMLRender
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HTMLTemplRenderer) Instance(s string, d any) render.Render {
|
|
||||||
templData, ok := d.(templ.Component)
|
|
||||||
if !ok {
|
|
||||||
if r.FallbackHtmlRenderer != nil {
|
|
||||||
return r.FallbackHtmlRenderer.Instance(s, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Renderer{
|
|
||||||
Ctx: context.Background(),
|
|
||||||
Status: -1,
|
|
||||||
Component: templData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGinTemplRenderer(ctx context.Context, status int, component templ.Component) *Renderer {
|
|
||||||
return &Renderer{
|
|
||||||
Ctx: ctx,
|
|
||||||
Status: status,
|
|
||||||
Component: component,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Renderer struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Status int
|
|
||||||
Component templ.Component
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Renderer) Render(w http.ResponseWriter) error {
|
|
||||||
t.WriteContentType(w)
|
|
||||||
if t.Status != -1 {
|
|
||||||
w.WriteHeader(t.Status)
|
|
||||||
}
|
|
||||||
if t.Component != nil {
|
|
||||||
return t.Component.Render(t.Ctx, w)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Renderer) WriteContentType(w http.ResponseWriter) {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"fastbin/internal/pkg/env"
|
|
||||||
"fastbin/internal/web/views"
|
|
||||||
)
|
|
||||||
|
|
||||||
type api_write_response struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type api_read_response struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServer(port int) *http.Server {
|
|
||||||
API_URL := env.GetEnv("API_URL", "localhost:8080")
|
|
||||||
|
|
||||||
r := gin.Default()
|
|
||||||
|
|
||||||
engineHTMLRenderer := r.HTMLRender
|
|
||||||
r.HTMLRender = &HTMLTemplRenderer{FallbackHtmlRenderer: engineHTMLRenderer}
|
|
||||||
|
|
||||||
r.StaticFS("/assets", http.FS(Files))
|
|
||||||
|
|
||||||
r.GET("/", func(ctx *gin.Context) {
|
|
||||||
ctx.HTML(http.StatusOK, "", views.Write())
|
|
||||||
})
|
|
||||||
|
|
||||||
r.POST("/", func(ctx *gin.Context) {
|
|
||||||
|
|
||||||
text := ctx.Request.FormValue("text")
|
|
||||||
postBody, _ := json.Marshal(map[string]string{
|
|
||||||
"text": text,
|
|
||||||
})
|
|
||||||
|
|
||||||
url := API_URL + "/write"
|
|
||||||
response, err := http.Post(url, "application/json", bytes.NewBuffer(postBody))
|
|
||||||
if err != nil {
|
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
|
||||||
}
|
|
||||||
|
|
||||||
var api_res api_write_response
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
|
||||||
|
|
||||||
err = decoder.Decode(&api_res)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.StatusCode == http.StatusOK {
|
|
||||||
ctx.Writer.Header().Add("Hx-Redirect", api_res.Key)
|
|
||||||
} else {
|
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/404", func(ctx *gin.Context) {
|
|
||||||
ctx.HTML(http.StatusOK, "", views.NotFound())
|
|
||||||
})
|
|
||||||
r.GET("/500", func(ctx *gin.Context) {
|
|
||||||
ctx.HTML(http.StatusOK, "", views.ServerError())
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/:key", func(ctx *gin.Context) {
|
|
||||||
url := API_URL + "/read/" + ctx.Param("key")
|
|
||||||
|
|
||||||
response, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
|
||||||
}
|
|
||||||
|
|
||||||
var api_res api_read_response
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
|
||||||
|
|
||||||
err = decoder.Decode(&api_res)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.StatusCode == http.StatusOK {
|
|
||||||
ctx.HTML(http.StatusOK, "", views.Read(api_res.Text))
|
|
||||||
} else if response.StatusCode == http.StatusNotFound {
|
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, "/404")
|
|
||||||
} else {
|
|
||||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%d", port),
|
|
||||||
Handler: r,
|
|
||||||
IdleTimeout: time.Minute,
|
|
||||||
ReadTimeout: 10 * time.Second,
|
|
||||||
WriteTimeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
return server
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package views
|
|
||||||
|
|
||||||
templ NotFound() {
|
|
||||||
@Base("fastbin") {
|
|
||||||
@Header() {
|
|
||||||
<a href="/" class="inline-block text-center align-middle">
|
|
||||||
@Button() {
|
|
||||||
<span class="material-symbols-outlined">note_add</span>
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
<div class="flex flex-1 w-full text-white text-xl">
|
|
||||||
<div class="h-full px-2.5 select-none">></div>
|
|
||||||
<div class="flex w-full h-full bg-transparent resize-none text-white justify-center items-center text-8xl"><div>404: Page Not Found. </div></div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package views
|
|
||||||
|
|
||||||
templ ServerError() {
|
|
||||||
@Base("fastbin") {
|
|
||||||
@Header() {
|
|
||||||
<a href="/" class="inline-block text-center align-middle">
|
|
||||||
@Button() {
|
|
||||||
<span class="material-symbols-outlined">note_add</span>
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
<div class="flex flex-1 w-full text-white text-xl">
|
|
||||||
<div class="h-full px-2.5 select-none">></div>
|
|
||||||
<div class="flex w-full h-full bg-transparent resize-none text-white justify-center items-center text-8xl"><div>500: Internal Server Error</div></div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package views
|
|
||||||
|
|
||||||
templ Base(title string) {
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined" />
|
|
||||||
<script src="assets/js/htmx.min.js"></script>
|
|
||||||
<link rel="stylesheet" href="assets/css/output.css"/>
|
|
||||||
<meta name="description" content="fastbin: sharing code made faster" />
|
|
||||||
<link rel="icon" href="/assets/favicon.ico" />
|
|
||||||
<title>{ title }</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main class="w-dvw h-dvh bg-primary flex flex-col items-start justify-center">
|
|
||||||
{ children... }
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ Header() {
|
|
||||||
<div class="w-full flex justify-between items-center text-white border-b-2 border-b-border">
|
|
||||||
<div class="px-[20px] py-[10px] text-4xl italic select-none pointer-events-none">fastbin</div>
|
|
||||||
<div class="flex self-stretch items-stretch">
|
|
||||||
{ children... }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ Button() {
|
|
||||||
<div class="w-[70px] h-[70px] flex justify-center align-middle items-center text-center cursor-pointer select-none hover:bg-border/50">
|
|
||||||
{ children... }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package views
|
|
||||||
|
|
||||||
templ Read(text string) {
|
|
||||||
@Base("fastbin") {
|
|
||||||
@Header() {
|
|
||||||
<a href="/" class="inline-block text-center align-middle">
|
|
||||||
@Button() {
|
|
||||||
<span class="material-symbols-outlined">note_add</span>
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
<div class="flex flex-1 w-full text-white text-xl">
|
|
||||||
<div class="h-full px-2.5 select-none">></div>
|
|
||||||
<textarea required readonly name="text" id="input" autofocus wrap="off" spellcheck="false" class="flex-1 w-full h-full bg-transparent resize-none outline-none border-none text-white text-xl">{ text } </textarea>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package views
|
|
||||||
|
|
||||||
var onceHandle = templ.NewOnceHandle()
|
|
||||||
|
|
||||||
templ Write() {
|
|
||||||
@Base("fastbin") {
|
|
||||||
<form id="input_form" hx-post="/" class="w-dvw h-dvh bg-primary flex flex-col items-start justify-center">
|
|
||||||
@Header() {
|
|
||||||
<a href="/" class="inline-block text-center align-middle">
|
|
||||||
@Button() {
|
|
||||||
<span class="material-symbols-outlined">note_add</span>
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
<a>
|
|
||||||
<button type="submit">
|
|
||||||
@Button() {
|
|
||||||
<span class="material-symbols-outlined">save</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
<script type="text/javascript">
|
|
||||||
function keyDownHandler(e) {
|
|
||||||
if (e.key === "Tab") {
|
|
||||||
e.preventDefault()
|
|
||||||
e.currentTarget.setRangeText(
|
|
||||||
'\t',
|
|
||||||
e.currentTarget.selectionStart,
|
|
||||||
e.currentTarget.selectionStart,
|
|
||||||
'end'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div class="flex flex-1 w-full text-white text-xl">
|
|
||||||
<div class="h-full px-2.5 select-none">></div>
|
|
||||||
<textarea required name="text" id="input" onkeydown="keyDownHandler(event)" autofocus wrap="off" spellcheck="false" class="flex-1 w-full h-full bg-transparent resize-none outline-none border-none text-white text-xl"></textarea>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
10
lib/fauna-client.ts
Normal file
10
lib/fauna-client.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import faunadb from "faunadb";
|
||||||
|
|
||||||
|
const client = new faunadb.Client({
|
||||||
|
secret: process.env.FAUNA_ADMIN_KEY || "",
|
||||||
|
domain: "db.fauna.com",
|
||||||
|
port: 443,
|
||||||
|
scheme: "https",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default client;
|
||||||
5
next-env.d.ts
vendored
Normal file
5
next-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||||
6
next.config.js
Normal file
6
next.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
30
package.json
Normal file
30
package.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "fastbin",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.11.1",
|
||||||
|
"@emotion/styled": "^11.11.0",
|
||||||
|
"@material-ui/core": "^4.12.3",
|
||||||
|
"@material-ui/icons": "^4.11.2",
|
||||||
|
"@mui/material": "^5.4.3",
|
||||||
|
"faunadb": "^4.5.2",
|
||||||
|
"highlight.js": "^11.4.0",
|
||||||
|
"next": "12.1.0",
|
||||||
|
"react": "17.0.2",
|
||||||
|
"react-dom": "17.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "17.0.18",
|
||||||
|
"@types/react": "17.0.39",
|
||||||
|
"eslint": "8.9.0",
|
||||||
|
"eslint-config-next": "12.1.0",
|
||||||
|
"typescript": "4.5.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
104
pages/[id].tsx
Normal file
104
pages/[id].tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { useEffect, createRef } from 'react'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import hljs from 'highlight.js'
|
||||||
|
|
||||||
|
import styles from '../styles/Viewer.module.css'
|
||||||
|
import header_styles from '../styles/Header.module.css'
|
||||||
|
import 'highlight.js/styles/atom-one-dark.css';
|
||||||
|
|
||||||
|
import NoteAdd from '@material-ui/icons/NoteAdd'
|
||||||
|
import { GetServerSidePropsContext } from 'next'
|
||||||
|
import { getData } from './api/get/[id]'
|
||||||
|
|
||||||
|
|
||||||
|
const Viewer = ({ code }: { code: string }) => {
|
||||||
|
|
||||||
|
const codeRef = createRef<HTMLTextAreaElement>();
|
||||||
|
const router = useRouter()
|
||||||
|
const lines = code.split('\n');
|
||||||
|
const html = hljs.highlightAuto(code);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (codeRef.current)
|
||||||
|
codeRef.current.innerHTML = html.value;
|
||||||
|
}, [html, codeRef])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = (event: KeyboardEvent) => {
|
||||||
|
if (event.code === "KeyN" && event.shiftKey === true) {
|
||||||
|
event.preventDefault()
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', listener)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', listener)
|
||||||
|
}
|
||||||
|
}, [router])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Head>
|
||||||
|
<title>fastbin</title>
|
||||||
|
<meta name="description" content="fastbin: sharing code made faster" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<div className={header_styles["header"]}>
|
||||||
|
<div className={header_styles["logo"]}> fastbin </div>
|
||||||
|
<div className={header_styles["buttons-container"]}>
|
||||||
|
<div
|
||||||
|
className={header_styles["buttons"]}
|
||||||
|
onClick={() => router.push('/')}
|
||||||
|
><NoteAdd /></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles['viewer']}>
|
||||||
|
<code className={styles["line-numbers"]}>
|
||||||
|
{
|
||||||
|
lines.map((_line, index) => <pre key={index}> {index + 1} </pre>)
|
||||||
|
}
|
||||||
|
</code>
|
||||||
|
<pre className={styles["code"]}>
|
||||||
|
<code ref={codeRef}>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
const id = context.params?.id
|
||||||
|
if (!id)
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: '/',
|
||||||
|
permanent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let data = await getData(id.toString())
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
code: data.data?.code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: '/',
|
||||||
|
permanent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Viewer
|
||||||
8
pages/_app.tsx
Normal file
8
pages/_app.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import '../styles/globals.css'
|
||||||
|
import type { AppProps } from 'next/app'
|
||||||
|
|
||||||
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
return <Component {...pageProps} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyApp
|
||||||
15
pages/_document.tsx
Normal file
15
pages/_document.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Html, Head, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
export default function Document() {
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head>
|
||||||
|
{/*<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" key="material-icons"></link>**/}
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
||||||
27
pages/api/get/[id].ts
Normal file
27
pages/api/get/[id].ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { Collection, Get, Ref, Time } from "faunadb";
|
||||||
|
import client from "../../../lib/fauna-client";
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FaunaQueryResponse = {
|
||||||
|
ref?: typeof Ref;
|
||||||
|
ts?: typeof Time;
|
||||||
|
data?: Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<Data>
|
||||||
|
) {
|
||||||
|
const id = req.query["id"];
|
||||||
|
getData(id.toString())
|
||||||
|
.then((ret) => res.status(200).json({ code: ret?.data?.code || "" }))
|
||||||
|
.catch(() => res.status(404).send({ code: "" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getData(id: string) {
|
||||||
|
return client.query<FaunaQueryResponse>(Get(Ref(Collection("data"), id)));
|
||||||
|
}
|
||||||
27
pages/api/new.ts
Normal file
27
pages/api/new.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import faunadb from "faunadb";
|
||||||
|
import client from "../../lib/fauna-client";
|
||||||
|
|
||||||
|
const q = faunadb.query;
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<Data>
|
||||||
|
) {
|
||||||
|
if (req.method !== "POST") res.status(404).send({ id: "" });
|
||||||
|
|
||||||
|
const code = req.body.data;
|
||||||
|
|
||||||
|
client
|
||||||
|
.query<any>(q.Create(q.Collection("data"), { data: { code: code } }))
|
||||||
|
.then((response) => {
|
||||||
|
res.status(200).json({ id: response?.ref?.id });
|
||||||
|
})
|
||||||
|
.catch((_error) => {
|
||||||
|
res.status(404).send({ id: "" });
|
||||||
|
});
|
||||||
|
}
|
||||||
114
pages/index.tsx
Normal file
114
pages/index.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { KeyboardEventHandler, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import type { NextPage } from 'next'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
|
import styles from '../styles/Editor.module.css'
|
||||||
|
import header_styles from '../styles/Header.module.css'
|
||||||
|
|
||||||
|
import Save from '@material-ui/icons/Save'
|
||||||
|
import NoteAdd from '@material-ui/icons/NoteAdd'
|
||||||
|
|
||||||
|
import { Snackbar } from '@mui/material'
|
||||||
|
|
||||||
|
const Home: NextPage = () => {
|
||||||
|
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
||||||
|
const codeRef = useRef<HTMLTextAreaElement>(null)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const save = useCallback(() => {
|
||||||
|
setUploading(true);
|
||||||
|
fetch('/api/new', {
|
||||||
|
'method': 'POST',
|
||||||
|
'headers': {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
'body': JSON.stringify({ data: codeRef.current?.value })
|
||||||
|
}).then(res => res.json())
|
||||||
|
.then(({ id }) => router.push(`/${id}`))
|
||||||
|
.catch(() => router.push('/'))
|
||||||
|
}, [router])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = (event: KeyboardEvent) => {
|
||||||
|
if (event.code === "KeyS" && event.ctrlKey === true) {
|
||||||
|
event.preventDefault()
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
if (event.code === "KeyN" && event.shiftKey === true) {
|
||||||
|
event.preventDefault()
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', listener)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', listener)
|
||||||
|
}
|
||||||
|
}, [save, router])
|
||||||
|
|
||||||
|
const keyDownHandler: KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
|
||||||
|
if (e.key === "Tab") {
|
||||||
|
e.preventDefault()
|
||||||
|
e.currentTarget.setRangeText(
|
||||||
|
'\t',
|
||||||
|
e.currentTarget.selectionStart,
|
||||||
|
e.currentTarget.selectionStart,
|
||||||
|
'end'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (codeRef.current)
|
||||||
|
codeRef.current.focus();
|
||||||
|
}, [codeRef])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Head>
|
||||||
|
<title>fastbin</title>
|
||||||
|
<meta name="description" content="fastbin: sharing code made faster" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<div className={header_styles["header"]}>
|
||||||
|
<div className={header_styles["logo"]}> fastbin </div>
|
||||||
|
<div className={header_styles["buttons-container"]}>
|
||||||
|
<div
|
||||||
|
className={header_styles["buttons"]}
|
||||||
|
onClick={() => router.push('/')}
|
||||||
|
>
|
||||||
|
<NoteAdd />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={header_styles["buttons"]}
|
||||||
|
onClick={save}
|
||||||
|
>
|
||||||
|
<Save />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.editor}>
|
||||||
|
<span className={styles["line-numbers"]}>
|
||||||
|
{">"}
|
||||||
|
</span>
|
||||||
|
<textarea
|
||||||
|
onKeyDown={keyDownHandler}
|
||||||
|
spellCheck={false}
|
||||||
|
wrap="off"
|
||||||
|
ref={codeRef}
|
||||||
|
placeholder={"Type Someting Here...\nCtrl + S to Save Document\nShift + N for New Document\n:)"}
|
||||||
|
className={styles["code-editor"]}>
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
<Snackbar open={uploading}><div className={styles.toast}>Uploading Document ...</div></Snackbar>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
4
public/vercel.svg
Normal file
4
public/vercel.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
45
styles/Editor.module.css
Normal file
45
styles/Editor.module.css
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: white;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
display: flex;
|
||||||
|
color: white;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor .line-numbers {
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-editor {
|
||||||
|
flex: 1;
|
||||||
|
padding: 3px;
|
||||||
|
overflow-x: auto;
|
||||||
|
resize: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
padding: 0.5em;
|
||||||
|
padding-left: 0.75em;
|
||||||
|
padding-right: 0.75em;
|
||||||
|
background-color: rgb(0, 94, 255);
|
||||||
|
font-size:x-large;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
33
styles/Header.module.css
Normal file
33
styles/Header.module.css
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
color: white;
|
||||||
|
border-bottom-color: var(--border-color, white);
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
padding: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons:hover {
|
||||||
|
background-color: rgba(69, 69, 69, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
font-size: xx-large;
|
||||||
|
font-style: italic;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
33
styles/Viewer.module.css
Normal file
33
styles/Viewer.module.css
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: white;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer {
|
||||||
|
display: flex;
|
||||||
|
color: white;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
padding-top: 10px;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers {
|
||||||
|
padding-top: 3px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
flex: 1;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
55
styles/globals.css
Normal file
55
styles/globals.css
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
:root {
|
||||||
|
--bg-color: rgb(29, 29, 29);
|
||||||
|
--border-color: rgb(69, 69, 69);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#__next {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||||
|
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--bg-color) white;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-button {
|
||||||
|
display: none;
|
||||||
|
background: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-corner {
|
||||||
|
background: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
border: 3px solid black;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
13
syntax.env
13
syntax.env
|
|
@ -1,13 +0,0 @@
|
||||||
# API_INTERNAL_PORT=8080 # When using without docker
|
|
||||||
# KEYGEN_INTERNAL_PORT=8081 # When using without docker
|
|
||||||
|
|
||||||
API_URL=http://api-server
|
|
||||||
|
|
||||||
KEYGEN_PORT=8080
|
|
||||||
KEYGEN_HOST=localhost
|
|
||||||
|
|
||||||
DB_PORT=5432
|
|
||||||
DB_HOST=localhost
|
|
||||||
DB_DATABASE=fastbin
|
|
||||||
DB_USERNAME=username
|
|
||||||
DB_PASSWORD=password
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
"./internal/**/*.{go,js,templ,html}"
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
"primary": "rgb(var(--primary-color) / <alpha-value>)",
|
|
||||||
"border": "rgb(var(--border-color) / <alpha-value>)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
||||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue