Compare commits
4 Commits
main
...
2c2c3f0158
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c2c3f0158 | |||
| 1adbf021db | |||
| d73343457d | |||
| e9230e6240 |
46
.github/workflows/docker.yaml
vendored
Normal file
46
.github/workflows/docker.yaml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: quay.io/buildah/stable
|
||||||
|
options: --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/fuse:rw --privileged
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUILDAH_ISOLATION: chroot
|
||||||
|
STORAGE_DRIVER: vfs
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install Node.js
|
||||||
|
run: dnf install -y nodejs git
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ssh-user: ${{ secrets.REGISTRY_USER }}
|
||||||
|
token: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Login to Registry
|
||||||
|
run: buildah login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASSWORD }} git.kocoder.xyz
|
||||||
|
|
||||||
|
- name: Generate Tag
|
||||||
|
id: tag
|
||||||
|
run: |
|
||||||
|
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||||
|
echo "value=${{ github.ref_name }}-$(date +%s)-${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Buildah Build
|
||||||
|
run: |
|
||||||
|
buildah build -t ${{ steps.tag.outputs.value }} .
|
||||||
|
buildah tag ${{ steps.tag.outputs.value }} latest
|
||||||
|
|
||||||
|
- name: Push Docker Images
|
||||||
|
run: |
|
||||||
|
buildah push ${{ steps.tag.outputs.value }} docker://git.kocoder.xyz/vt/solid-demo:${{ steps.tag.outputs.value }}
|
||||||
|
buildah push latest docker://git.kocoder.xyz/vt/solid-demo:latest
|
||||||
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
FROM golang:1.26-bookworm AS base
|
||||||
|
|
||||||
|
# Move to working directory /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the go.mod and go.sum files to the /app directory
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy the entire source code into the container
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN go build -o shortener
|
||||||
|
|
||||||
|
FROM golang:1.26-bookworm AS runner
|
||||||
|
|
||||||
|
COPY --from=base /app/shortener /app/shortener
|
||||||
|
|
||||||
|
# Document the port that may need to be published
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["/app/shortener"]
|
||||||
18
buf.gen.yaml
Normal file
18
buf.gen.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
version: v2
|
||||||
|
plugins:
|
||||||
|
- local: [go, tool, protoc-gen-go]
|
||||||
|
out: internal
|
||||||
|
opt: paths=source_relative
|
||||||
|
- local: [go, tool, protoc-gen-connect-go]
|
||||||
|
out: internal
|
||||||
|
opt:
|
||||||
|
- paths=source_relative
|
||||||
|
- simple
|
||||||
|
managed:
|
||||||
|
enabled: true
|
||||||
|
override:
|
||||||
|
- file_option: go_package_prefix
|
||||||
|
value: git.kocoder.xyz/vt/shortener/internal
|
||||||
|
disable:
|
||||||
|
- file_option: go_package
|
||||||
|
module: buf.build/bufbuild/protovalidate
|
||||||
6
buf.lock
Normal file
6
buf.lock
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Generated by buf. DO NOT EDIT.
|
||||||
|
version: v2
|
||||||
|
deps:
|
||||||
|
- name: buf.build/bufbuild/protovalidate
|
||||||
|
commit: 50325440f8f24053b047484a6bf60b76
|
||||||
|
digest: b5:74cb6f5c0853c3c10aafc701614194bbd63326bdb8ef4068214454b8894b03ba4113e04b3a33a8321cdf05336e37db4dc14a5e2495db8462566914f36086ba31
|
||||||
9
buf.yaml
Normal file
9
buf.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: v2
|
||||||
|
deps:
|
||||||
|
- buf.build/bufbuild/protovalidate
|
||||||
|
lint:
|
||||||
|
use:
|
||||||
|
- STANDARD
|
||||||
|
breaking:
|
||||||
|
use:
|
||||||
|
- FILE
|
||||||
586
internal/proto/shorten/v1/shorten.pb.go
Normal file
586
internal/proto/shorten/v1/shorten.pb.go
Normal file
@@ -0,0 +1,586 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc (unknown)
|
||||||
|
// source: proto/shorten/v1/shorten.proto
|
||||||
|
|
||||||
|
package shortenv1
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 CreateURLRedirectionRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
ShortCode string `protobuf:"bytes,1,opt,name=short_code,json=shortCode,proto3" json:"short_code,omitempty"`
|
||||||
|
Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateURLRedirectionRequest) Reset() {
|
||||||
|
*x = CreateURLRedirectionRequest{}
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateURLRedirectionRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CreateURLRedirectionRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CreateURLRedirectionRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_shorten_v1_shorten_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 CreateURLRedirectionRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CreateURLRedirectionRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateURLRedirectionRequest) GetShortCode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ShortCode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateURLRedirectionRequest) GetUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Url
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateURLRedirectionResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateURLRedirectionResponse) Reset() {
|
||||||
|
*x = CreateURLRedirectionResponse{}
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateURLRedirectionResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CreateURLRedirectionResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CreateURLRedirectionResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_shorten_v1_shorten_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 CreateURLRedirectionResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CreateURLRedirectionResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateURLRedirectionResponse) GetOk() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ok
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type URLRedirection struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UrlId int32 `protobuf:"varint,1,opt,name=url_id,json=urlId,proto3" json:"url_id,omitempty"`
|
||||||
|
ShortCode string `protobuf:"bytes,3,opt,name=short_code,json=shortCode,proto3" json:"short_code,omitempty"`
|
||||||
|
Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
|
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||||
|
ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
|
||||||
|
IsActive bool `protobuf:"varint,6,opt,name=is_active,json=isActive,proto3" json:"is_active,omitempty"`
|
||||||
|
ClickCountByDay []int32 `protobuf:"varint,7,rep,packed,name=click_count_by_day,json=clickCountByDay,proto3" json:"click_count_by_day,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *URLRedirection) Reset() {
|
||||||
|
*x = URLRedirection{}
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *URLRedirection) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*URLRedirection) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *URLRedirection) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[2]
|
||||||
|
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 URLRedirection.ProtoReflect.Descriptor instead.
|
||||||
|
func (*URLRedirection) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *URLRedirection) GetUrlId() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UrlId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *URLRedirection) GetShortCode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ShortCode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *URLRedirection) GetUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Url
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *URLRedirection) GetCreatedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.CreatedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *URLRedirection) GetExpiresAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.ExpiresAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *URLRedirection) GetIsActive() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsActive
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *URLRedirection) GetClickCountByDay() []int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ClickCountByDay
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListURLRedirectionsRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListURLRedirectionsRequest) Reset() {
|
||||||
|
*x = ListURLRedirectionsRequest{}
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListURLRedirectionsRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListURLRedirectionsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListURLRedirectionsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[3]
|
||||||
|
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 ListURLRedirectionsRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListURLRedirectionsRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListURLRedirectionsResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UrlRedirections []*URLRedirection `protobuf:"bytes,1,rep,name=url_redirections,json=urlRedirections,proto3" json:"url_redirections,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListURLRedirectionsResponse) Reset() {
|
||||||
|
*x = ListURLRedirectionsResponse{}
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListURLRedirectionsResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListURLRedirectionsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListURLRedirectionsResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[4]
|
||||||
|
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 ListURLRedirectionsResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListURLRedirectionsResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListURLRedirectionsResponse) GetUrlRedirections() []*URLRedirection {
|
||||||
|
if x != nil {
|
||||||
|
return x.UrlRedirections
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeactivateURLRedirectionRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UrlId int32 `protobuf:"varint,1,opt,name=url_id,json=urlId,proto3" json:"url_id,omitempty"`
|
||||||
|
IsActive bool `protobuf:"varint,2,opt,name=is_active,json=isActive,proto3" json:"is_active,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeactivateURLRedirectionRequest) Reset() {
|
||||||
|
*x = DeactivateURLRedirectionRequest{}
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeactivateURLRedirectionRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeactivateURLRedirectionRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeactivateURLRedirectionRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[5]
|
||||||
|
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 DeactivateURLRedirectionRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeactivateURLRedirectionRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeactivateURLRedirectionRequest) GetUrlId() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UrlId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeactivateURLRedirectionRequest) GetIsActive() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsActive
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeactivateURLRedirectionResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeactivateURLRedirectionResponse) Reset() {
|
||||||
|
*x = DeactivateURLRedirectionResponse{}
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeactivateURLRedirectionResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeactivateURLRedirectionResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeactivateURLRedirectionResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[6]
|
||||||
|
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 DeactivateURLRedirectionResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeactivateURLRedirectionResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeactivateURLRedirectionResponse) GetOk() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ok
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteURLRedirectionRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UrlId int32 `protobuf:"varint,1,opt,name=url_id,json=urlId,proto3" json:"url_id,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteURLRedirectionRequest) Reset() {
|
||||||
|
*x = DeleteURLRedirectionRequest{}
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteURLRedirectionRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeleteURLRedirectionRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeleteURLRedirectionRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[7]
|
||||||
|
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 DeleteURLRedirectionRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeleteURLRedirectionRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteURLRedirectionRequest) GetUrlId() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UrlId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteURLRedirectionResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteURLRedirectionResponse) Reset() {
|
||||||
|
*x = DeleteURLRedirectionResponse{}
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[8]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteURLRedirectionResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeleteURLRedirectionResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeleteURLRedirectionResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_shorten_v1_shorten_proto_msgTypes[8]
|
||||||
|
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 DeleteURLRedirectionResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeleteURLRedirectionResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescGZIP(), []int{8}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteURLRedirectionResponse) GetOk() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ok
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_proto_shorten_v1_shorten_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_proto_shorten_v1_shorten_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x1eproto/shorten/v1/shorten.proto\x12\x10proto.shorten.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"b\n" +
|
||||||
|
"\x1bCreateURLRedirectionRequest\x12'\n" +
|
||||||
|
"\n" +
|
||||||
|
"short_code\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x98\x01\n" +
|
||||||
|
"R\tshortCode\x12\x1a\n" +
|
||||||
|
"\x03url\x18\x02 \x01(\tB\b\xbaH\x05r\x03\x88\x01\x01R\x03url\".\n" +
|
||||||
|
"\x1cCreateURLRedirectionResponse\x12\x0e\n" +
|
||||||
|
"\x02ok\x18\x01 \x01(\bR\x02ok\"\x98\x02\n" +
|
||||||
|
"\x0eURLRedirection\x12\x15\n" +
|
||||||
|
"\x06url_id\x18\x01 \x01(\x05R\x05urlId\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"short_code\x18\x03 \x01(\tR\tshortCode\x12\x10\n" +
|
||||||
|
"\x03url\x18\x02 \x01(\tR\x03url\x129\n" +
|
||||||
|
"\n" +
|
||||||
|
"created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" +
|
||||||
|
"\n" +
|
||||||
|
"expires_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12\x1b\n" +
|
||||||
|
"\tis_active\x18\x06 \x01(\bR\bisActive\x12+\n" +
|
||||||
|
"\x12click_count_by_day\x18\a \x03(\x05R\x0fclickCountByDay\"\x1c\n" +
|
||||||
|
"\x1aListURLRedirectionsRequest\"j\n" +
|
||||||
|
"\x1bListURLRedirectionsResponse\x12K\n" +
|
||||||
|
"\x10url_redirections\x18\x01 \x03(\v2 .proto.shorten.v1.URLRedirectionR\x0furlRedirections\"U\n" +
|
||||||
|
"\x1fDeactivateURLRedirectionRequest\x12\x15\n" +
|
||||||
|
"\x06url_id\x18\x01 \x01(\x05R\x05urlId\x12\x1b\n" +
|
||||||
|
"\tis_active\x18\x02 \x01(\bR\bisActive\"2\n" +
|
||||||
|
" DeactivateURLRedirectionResponse\x12\x0e\n" +
|
||||||
|
"\x02ok\x18\x01 \x01(\bR\x02ok\"4\n" +
|
||||||
|
"\x1bDeleteURLRedirectionRequest\x12\x15\n" +
|
||||||
|
"\x06url_id\x18\x01 \x01(\x05R\x05urlId\".\n" +
|
||||||
|
"\x1cDeleteURLRedirectionResponse\x12\x0e\n" +
|
||||||
|
"\x02ok\x18\x01 \x01(\bR\x02ok2\xfe\x03\n" +
|
||||||
|
"\x0eShortenService\x12w\n" +
|
||||||
|
"\x14CreateURLRedirection\x12-.proto.shorten.v1.CreateURLRedirectionRequest\x1a..proto.shorten.v1.CreateURLRedirectionResponse\"\x00\x12t\n" +
|
||||||
|
"\x13ListURLRedirections\x12,.proto.shorten.v1.ListURLRedirectionsRequest\x1a-.proto.shorten.v1.ListURLRedirectionsResponse\"\x00\x12\x83\x01\n" +
|
||||||
|
"\x18DeactivateURLRedirection\x121.proto.shorten.v1.DeactivateURLRedirectionRequest\x1a2.proto.shorten.v1.DeactivateURLRedirectionResponse\"\x00\x12w\n" +
|
||||||
|
"\x14DeleteURLRedirection\x12-.proto.shorten.v1.DeleteURLRedirectionRequest\x1a..proto.shorten.v1.DeleteURLRedirectionResponse\"\x00B\xc8\x01\n" +
|
||||||
|
"\x14com.proto.shorten.v1B\fShortenProtoP\x01Z@git.kocoder.xyz/vt/shortener/internal/proto/shorten/v1;shortenv1\xa2\x02\x03PSX\xaa\x02\x10Proto.Shorten.V1\xca\x02\x10Proto\\Shorten\\V1\xe2\x02\x1cProto\\Shorten\\V1\\GPBMetadata\xea\x02\x12Proto::Shorten::V1b\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_proto_shorten_v1_shorten_proto_rawDescOnce sync.Once
|
||||||
|
file_proto_shorten_v1_shorten_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_proto_shorten_v1_shorten_proto_rawDescGZIP() []byte {
|
||||||
|
file_proto_shorten_v1_shorten_proto_rawDescOnce.Do(func() {
|
||||||
|
file_proto_shorten_v1_shorten_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_shorten_v1_shorten_proto_rawDesc), len(file_proto_shorten_v1_shorten_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_proto_shorten_v1_shorten_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_proto_shorten_v1_shorten_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||||
|
var file_proto_shorten_v1_shorten_proto_goTypes = []any{
|
||||||
|
(*CreateURLRedirectionRequest)(nil), // 0: proto.shorten.v1.CreateURLRedirectionRequest
|
||||||
|
(*CreateURLRedirectionResponse)(nil), // 1: proto.shorten.v1.CreateURLRedirectionResponse
|
||||||
|
(*URLRedirection)(nil), // 2: proto.shorten.v1.URLRedirection
|
||||||
|
(*ListURLRedirectionsRequest)(nil), // 3: proto.shorten.v1.ListURLRedirectionsRequest
|
||||||
|
(*ListURLRedirectionsResponse)(nil), // 4: proto.shorten.v1.ListURLRedirectionsResponse
|
||||||
|
(*DeactivateURLRedirectionRequest)(nil), // 5: proto.shorten.v1.DeactivateURLRedirectionRequest
|
||||||
|
(*DeactivateURLRedirectionResponse)(nil), // 6: proto.shorten.v1.DeactivateURLRedirectionResponse
|
||||||
|
(*DeleteURLRedirectionRequest)(nil), // 7: proto.shorten.v1.DeleteURLRedirectionRequest
|
||||||
|
(*DeleteURLRedirectionResponse)(nil), // 8: proto.shorten.v1.DeleteURLRedirectionResponse
|
||||||
|
(*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp
|
||||||
|
}
|
||||||
|
var file_proto_shorten_v1_shorten_proto_depIdxs = []int32{
|
||||||
|
9, // 0: proto.shorten.v1.URLRedirection.created_at:type_name -> google.protobuf.Timestamp
|
||||||
|
9, // 1: proto.shorten.v1.URLRedirection.expires_at:type_name -> google.protobuf.Timestamp
|
||||||
|
2, // 2: proto.shorten.v1.ListURLRedirectionsResponse.url_redirections:type_name -> proto.shorten.v1.URLRedirection
|
||||||
|
0, // 3: proto.shorten.v1.ShortenService.CreateURLRedirection:input_type -> proto.shorten.v1.CreateURLRedirectionRequest
|
||||||
|
3, // 4: proto.shorten.v1.ShortenService.ListURLRedirections:input_type -> proto.shorten.v1.ListURLRedirectionsRequest
|
||||||
|
5, // 5: proto.shorten.v1.ShortenService.DeactivateURLRedirection:input_type -> proto.shorten.v1.DeactivateURLRedirectionRequest
|
||||||
|
7, // 6: proto.shorten.v1.ShortenService.DeleteURLRedirection:input_type -> proto.shorten.v1.DeleteURLRedirectionRequest
|
||||||
|
1, // 7: proto.shorten.v1.ShortenService.CreateURLRedirection:output_type -> proto.shorten.v1.CreateURLRedirectionResponse
|
||||||
|
4, // 8: proto.shorten.v1.ShortenService.ListURLRedirections:output_type -> proto.shorten.v1.ListURLRedirectionsResponse
|
||||||
|
6, // 9: proto.shorten.v1.ShortenService.DeactivateURLRedirection:output_type -> proto.shorten.v1.DeactivateURLRedirectionResponse
|
||||||
|
8, // 10: proto.shorten.v1.ShortenService.DeleteURLRedirection:output_type -> proto.shorten.v1.DeleteURLRedirectionResponse
|
||||||
|
7, // [7:11] is the sub-list for method output_type
|
||||||
|
3, // [3:7] is the sub-list for method input_type
|
||||||
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
|
3, // [3:3] is the sub-list for extension extendee
|
||||||
|
0, // [0:3] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_proto_shorten_v1_shorten_proto_init() }
|
||||||
|
func file_proto_shorten_v1_shorten_proto_init() {
|
||||||
|
if File_proto_shorten_v1_shorten_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_shorten_v1_shorten_proto_rawDesc), len(file_proto_shorten_v1_shorten_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 9,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_proto_shorten_v1_shorten_proto_goTypes,
|
||||||
|
DependencyIndexes: file_proto_shorten_v1_shorten_proto_depIdxs,
|
||||||
|
MessageInfos: file_proto_shorten_v1_shorten_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_proto_shorten_v1_shorten_proto = out.File
|
||||||
|
file_proto_shorten_v1_shorten_proto_goTypes = nil
|
||||||
|
file_proto_shorten_v1_shorten_proto_depIdxs = nil
|
||||||
|
}
|
||||||
216
internal/proto/shorten/v1/shortenv1connect/shorten.connect.go
Normal file
216
internal/proto/shorten/v1/shortenv1connect/shorten.connect.go
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// Source: proto/shorten/v1/shorten.proto
|
||||||
|
|
||||||
|
package shortenv1connect
|
||||||
|
|
||||||
|
import (
|
||||||
|
connect "connectrpc.com/connect"
|
||||||
|
context "context"
|
||||||
|
errors "errors"
|
||||||
|
v1 "git.kocoder.xyz/vt/shortener/internal/proto/shorten/v1"
|
||||||
|
http "net/http"
|
||||||
|
strings "strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file and the connect package are
|
||||||
|
// compatible. If you get a compiler error that this constant is not defined, this code was
|
||||||
|
// generated with a version of connect newer than the one compiled into your binary. You can fix the
|
||||||
|
// problem by either regenerating this code with an older version of connect or updating the connect
|
||||||
|
// version compiled into your binary.
|
||||||
|
const _ = connect.IsAtLeastVersion1_13_0
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ShortenServiceName is the fully-qualified name of the ShortenService service.
|
||||||
|
ShortenServiceName = "proto.shorten.v1.ShortenService"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||||
|
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
|
||||||
|
//
|
||||||
|
// Note that these are different from the fully-qualified method names used by
|
||||||
|
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||||
|
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||||
|
// period.
|
||||||
|
const (
|
||||||
|
// ShortenServiceCreateURLRedirectionProcedure is the fully-qualified name of the ShortenService's
|
||||||
|
// CreateURLRedirection RPC.
|
||||||
|
ShortenServiceCreateURLRedirectionProcedure = "/proto.shorten.v1.ShortenService/CreateURLRedirection"
|
||||||
|
// ShortenServiceListURLRedirectionsProcedure is the fully-qualified name of the ShortenService's
|
||||||
|
// ListURLRedirections RPC.
|
||||||
|
ShortenServiceListURLRedirectionsProcedure = "/proto.shorten.v1.ShortenService/ListURLRedirections"
|
||||||
|
// ShortenServiceDeactivateURLRedirectionProcedure is the fully-qualified name of the
|
||||||
|
// ShortenService's DeactivateURLRedirection RPC.
|
||||||
|
ShortenServiceDeactivateURLRedirectionProcedure = "/proto.shorten.v1.ShortenService/DeactivateURLRedirection"
|
||||||
|
// ShortenServiceDeleteURLRedirectionProcedure is the fully-qualified name of the ShortenService's
|
||||||
|
// DeleteURLRedirection RPC.
|
||||||
|
ShortenServiceDeleteURLRedirectionProcedure = "/proto.shorten.v1.ShortenService/DeleteURLRedirection"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShortenServiceClient is a client for the proto.shorten.v1.ShortenService service.
|
||||||
|
type ShortenServiceClient interface {
|
||||||
|
CreateURLRedirection(context.Context, *v1.CreateURLRedirectionRequest) (*v1.CreateURLRedirectionResponse, error)
|
||||||
|
ListURLRedirections(context.Context, *v1.ListURLRedirectionsRequest) (*v1.ListURLRedirectionsResponse, error)
|
||||||
|
// rpc GetURLRedirection(GetURLRedirectionRequest) returns
|
||||||
|
// (GetURLRedirectionResponse) {}
|
||||||
|
DeactivateURLRedirection(context.Context, *v1.DeactivateURLRedirectionRequest) (*v1.DeactivateURLRedirectionResponse, error)
|
||||||
|
DeleteURLRedirection(context.Context, *v1.DeleteURLRedirectionRequest) (*v1.DeleteURLRedirectionResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShortenServiceClient constructs a client for the proto.shorten.v1.ShortenService service. By
|
||||||
|
// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
|
||||||
|
// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
|
||||||
|
// connect.WithGRPC() or connect.WithGRPCWeb() options.
|
||||||
|
//
|
||||||
|
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
|
||||||
|
// http://api.acme.com or https://acme.com/grpc).
|
||||||
|
func NewShortenServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ShortenServiceClient {
|
||||||
|
baseURL = strings.TrimRight(baseURL, "/")
|
||||||
|
shortenServiceMethods := v1.File_proto_shorten_v1_shorten_proto.Services().ByName("ShortenService").Methods()
|
||||||
|
return &shortenServiceClient{
|
||||||
|
createURLRedirection: connect.NewClient[v1.CreateURLRedirectionRequest, v1.CreateURLRedirectionResponse](
|
||||||
|
httpClient,
|
||||||
|
baseURL+ShortenServiceCreateURLRedirectionProcedure,
|
||||||
|
connect.WithSchema(shortenServiceMethods.ByName("CreateURLRedirection")),
|
||||||
|
connect.WithClientOptions(opts...),
|
||||||
|
),
|
||||||
|
listURLRedirections: connect.NewClient[v1.ListURLRedirectionsRequest, v1.ListURLRedirectionsResponse](
|
||||||
|
httpClient,
|
||||||
|
baseURL+ShortenServiceListURLRedirectionsProcedure,
|
||||||
|
connect.WithSchema(shortenServiceMethods.ByName("ListURLRedirections")),
|
||||||
|
connect.WithClientOptions(opts...),
|
||||||
|
),
|
||||||
|
deactivateURLRedirection: connect.NewClient[v1.DeactivateURLRedirectionRequest, v1.DeactivateURLRedirectionResponse](
|
||||||
|
httpClient,
|
||||||
|
baseURL+ShortenServiceDeactivateURLRedirectionProcedure,
|
||||||
|
connect.WithSchema(shortenServiceMethods.ByName("DeactivateURLRedirection")),
|
||||||
|
connect.WithClientOptions(opts...),
|
||||||
|
),
|
||||||
|
deleteURLRedirection: connect.NewClient[v1.DeleteURLRedirectionRequest, v1.DeleteURLRedirectionResponse](
|
||||||
|
httpClient,
|
||||||
|
baseURL+ShortenServiceDeleteURLRedirectionProcedure,
|
||||||
|
connect.WithSchema(shortenServiceMethods.ByName("DeleteURLRedirection")),
|
||||||
|
connect.WithClientOptions(opts...),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortenServiceClient implements ShortenServiceClient.
|
||||||
|
type shortenServiceClient struct {
|
||||||
|
createURLRedirection *connect.Client[v1.CreateURLRedirectionRequest, v1.CreateURLRedirectionResponse]
|
||||||
|
listURLRedirections *connect.Client[v1.ListURLRedirectionsRequest, v1.ListURLRedirectionsResponse]
|
||||||
|
deactivateURLRedirection *connect.Client[v1.DeactivateURLRedirectionRequest, v1.DeactivateURLRedirectionResponse]
|
||||||
|
deleteURLRedirection *connect.Client[v1.DeleteURLRedirectionRequest, v1.DeleteURLRedirectionResponse]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateURLRedirection calls proto.shorten.v1.ShortenService.CreateURLRedirection.
|
||||||
|
func (c *shortenServiceClient) CreateURLRedirection(ctx context.Context, req *v1.CreateURLRedirectionRequest) (*v1.CreateURLRedirectionResponse, error) {
|
||||||
|
response, err := c.createURLRedirection.CallUnary(ctx, connect.NewRequest(req))
|
||||||
|
if response != nil {
|
||||||
|
return response.Msg, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListURLRedirections calls proto.shorten.v1.ShortenService.ListURLRedirections.
|
||||||
|
func (c *shortenServiceClient) ListURLRedirections(ctx context.Context, req *v1.ListURLRedirectionsRequest) (*v1.ListURLRedirectionsResponse, error) {
|
||||||
|
response, err := c.listURLRedirections.CallUnary(ctx, connect.NewRequest(req))
|
||||||
|
if response != nil {
|
||||||
|
return response.Msg, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeactivateURLRedirection calls proto.shorten.v1.ShortenService.DeactivateURLRedirection.
|
||||||
|
func (c *shortenServiceClient) DeactivateURLRedirection(ctx context.Context, req *v1.DeactivateURLRedirectionRequest) (*v1.DeactivateURLRedirectionResponse, error) {
|
||||||
|
response, err := c.deactivateURLRedirection.CallUnary(ctx, connect.NewRequest(req))
|
||||||
|
if response != nil {
|
||||||
|
return response.Msg, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteURLRedirection calls proto.shorten.v1.ShortenService.DeleteURLRedirection.
|
||||||
|
func (c *shortenServiceClient) DeleteURLRedirection(ctx context.Context, req *v1.DeleteURLRedirectionRequest) (*v1.DeleteURLRedirectionResponse, error) {
|
||||||
|
response, err := c.deleteURLRedirection.CallUnary(ctx, connect.NewRequest(req))
|
||||||
|
if response != nil {
|
||||||
|
return response.Msg, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShortenServiceHandler is an implementation of the proto.shorten.v1.ShortenService service.
|
||||||
|
type ShortenServiceHandler interface {
|
||||||
|
CreateURLRedirection(context.Context, *v1.CreateURLRedirectionRequest) (*v1.CreateURLRedirectionResponse, error)
|
||||||
|
ListURLRedirections(context.Context, *v1.ListURLRedirectionsRequest) (*v1.ListURLRedirectionsResponse, error)
|
||||||
|
// rpc GetURLRedirection(GetURLRedirectionRequest) returns
|
||||||
|
// (GetURLRedirectionResponse) {}
|
||||||
|
DeactivateURLRedirection(context.Context, *v1.DeactivateURLRedirectionRequest) (*v1.DeactivateURLRedirectionResponse, error)
|
||||||
|
DeleteURLRedirection(context.Context, *v1.DeleteURLRedirectionRequest) (*v1.DeleteURLRedirectionResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShortenServiceHandler builds an HTTP handler from the service implementation. It returns the
|
||||||
|
// path on which to mount the handler and the handler itself.
|
||||||
|
//
|
||||||
|
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
|
||||||
|
// and JSON codecs. They also support gzip compression.
|
||||||
|
func NewShortenServiceHandler(svc ShortenServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
||||||
|
shortenServiceMethods := v1.File_proto_shorten_v1_shorten_proto.Services().ByName("ShortenService").Methods()
|
||||||
|
shortenServiceCreateURLRedirectionHandler := connect.NewUnaryHandlerSimple(
|
||||||
|
ShortenServiceCreateURLRedirectionProcedure,
|
||||||
|
svc.CreateURLRedirection,
|
||||||
|
connect.WithSchema(shortenServiceMethods.ByName("CreateURLRedirection")),
|
||||||
|
connect.WithHandlerOptions(opts...),
|
||||||
|
)
|
||||||
|
shortenServiceListURLRedirectionsHandler := connect.NewUnaryHandlerSimple(
|
||||||
|
ShortenServiceListURLRedirectionsProcedure,
|
||||||
|
svc.ListURLRedirections,
|
||||||
|
connect.WithSchema(shortenServiceMethods.ByName("ListURLRedirections")),
|
||||||
|
connect.WithHandlerOptions(opts...),
|
||||||
|
)
|
||||||
|
shortenServiceDeactivateURLRedirectionHandler := connect.NewUnaryHandlerSimple(
|
||||||
|
ShortenServiceDeactivateURLRedirectionProcedure,
|
||||||
|
svc.DeactivateURLRedirection,
|
||||||
|
connect.WithSchema(shortenServiceMethods.ByName("DeactivateURLRedirection")),
|
||||||
|
connect.WithHandlerOptions(opts...),
|
||||||
|
)
|
||||||
|
shortenServiceDeleteURLRedirectionHandler := connect.NewUnaryHandlerSimple(
|
||||||
|
ShortenServiceDeleteURLRedirectionProcedure,
|
||||||
|
svc.DeleteURLRedirection,
|
||||||
|
connect.WithSchema(shortenServiceMethods.ByName("DeleteURLRedirection")),
|
||||||
|
connect.WithHandlerOptions(opts...),
|
||||||
|
)
|
||||||
|
return "/proto.shorten.v1.ShortenService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case ShortenServiceCreateURLRedirectionProcedure:
|
||||||
|
shortenServiceCreateURLRedirectionHandler.ServeHTTP(w, r)
|
||||||
|
case ShortenServiceListURLRedirectionsProcedure:
|
||||||
|
shortenServiceListURLRedirectionsHandler.ServeHTTP(w, r)
|
||||||
|
case ShortenServiceDeactivateURLRedirectionProcedure:
|
||||||
|
shortenServiceDeactivateURLRedirectionHandler.ServeHTTP(w, r)
|
||||||
|
case ShortenServiceDeleteURLRedirectionProcedure:
|
||||||
|
shortenServiceDeleteURLRedirectionHandler.ServeHTTP(w, r)
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedShortenServiceHandler returns CodeUnimplemented from all methods.
|
||||||
|
type UnimplementedShortenServiceHandler struct{}
|
||||||
|
|
||||||
|
func (UnimplementedShortenServiceHandler) CreateURLRedirection(context.Context, *v1.CreateURLRedirectionRequest) (*v1.CreateURLRedirectionResponse, error) {
|
||||||
|
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("proto.shorten.v1.ShortenService.CreateURLRedirection is not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedShortenServiceHandler) ListURLRedirections(context.Context, *v1.ListURLRedirectionsRequest) (*v1.ListURLRedirectionsResponse, error) {
|
||||||
|
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("proto.shorten.v1.ShortenService.ListURLRedirections is not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedShortenServiceHandler) DeactivateURLRedirection(context.Context, *v1.DeactivateURLRedirectionRequest) (*v1.DeactivateURLRedirectionResponse, error) {
|
||||||
|
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("proto.shorten.v1.ShortenService.DeactivateURLRedirection is not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedShortenServiceHandler) DeleteURLRedirection(context.Context, *v1.DeleteURLRedirectionRequest) (*v1.DeleteURLRedirectionResponse, error) {
|
||||||
|
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("proto.shorten.v1.ShortenService.DeleteURLRedirection is not implemented"))
|
||||||
|
}
|
||||||
83
internal/service/http.go
Normal file
83
internal/service/http.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/config"
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
conf *config.Config
|
||||||
|
db *database.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(conf *config.Config, db *database.Queries) *Server {
|
||||||
|
return &Server{
|
||||||
|
conf: conf,
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
ctx := req.Context()
|
||||||
|
path := req.URL.Path
|
||||||
|
|
||||||
|
path = strings.Trim(path, "/")
|
||||||
|
|
||||||
|
slog.InfoContext(ctx, "processing request", slog.String("path", path))
|
||||||
|
|
||||||
|
res, err := s.db.GetURLByShortCode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx, "failed to get url by short code", slog.String("path", path), slog.String("err", err.Error()))
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardedFor := req.Header.Get("X-Forwarded-For")
|
||||||
|
remoteAddr := req.RemoteAddr
|
||||||
|
|
||||||
|
if slices.Contains(s.conf.TRUSTED_PROXIES, strings.Split(remoteAddr, ":")[0]) {
|
||||||
|
if strings.Contains(forwardedFor, ", ") {
|
||||||
|
remoteAddr = strings.Split(forwardedFor, ", ")[0]
|
||||||
|
} else {
|
||||||
|
remoteAddr = forwardedFor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.InfoContext(ctx, "tracking click",
|
||||||
|
slog.String("ip", remoteAddr),
|
||||||
|
slog.String("user_agent", req.UserAgent()),
|
||||||
|
slog.String("referrer", req.Referer()),
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err = s.db.TrackClick(ctx, database.TrackClickParams{
|
||||||
|
UrlID: res.UrlID,
|
||||||
|
UserAgent: sql.NullString{
|
||||||
|
String: req.UserAgent(),
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Referrer: sql.NullString{
|
||||||
|
String: req.Referer(),
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
IpAddress: sql.NullString{
|
||||||
|
String: remoteAddr,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(ctx, "failed to track click", slog.String("err", err.Error()))
|
||||||
|
// Still continuing to redirect even if tracking fails, or we can return.
|
||||||
|
// Usually we still want to redirect the user so they reach their destination.
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.InfoContext(ctx, "redirecting to destination", slog.String("long_url", res.LongUrl))
|
||||||
|
|
||||||
|
http.Redirect(w, req, res.LongUrl, http.StatusFound)
|
||||||
|
}
|
||||||
105
internal/service/shorten_proto.go
Normal file
105
internal/service/shorten_proto.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/config"
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/database"
|
||||||
|
shortenv1 "git.kocoder.xyz/vt/shortener/internal/proto/shorten/v1"
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/proto/shorten/v1/shortenv1connect"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShortenerService struct {
|
||||||
|
conf *config.Config
|
||||||
|
db *database.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateURLRedirection implements [shortenv1connect.ShortenServiceHandler].
|
||||||
|
func (s *ShortenerService) CreateURLRedirection(ctx context.Context, req *shortenv1.CreateURLRedirectionRequest) (*shortenv1.CreateURLRedirectionResponse, error) {
|
||||||
|
_, err := s.db.CreateURL(ctx, database.CreateURLParams{
|
||||||
|
ShortCode: req.ShortCode,
|
||||||
|
LongUrl: req.Url,
|
||||||
|
IsActive: true,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Log(ctx, slog.LevelError, err.Error())
|
||||||
|
return &shortenv1.CreateURLRedirectionResponse{
|
||||||
|
Ok: false,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &shortenv1.CreateURLRedirectionResponse{
|
||||||
|
Ok: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeactivateURLRedirection implements [shortenv1connect.ShortenServiceHandler].
|
||||||
|
func (s *ShortenerService) DeactivateURLRedirection(ctx context.Context, req *shortenv1.DeactivateURLRedirectionRequest) (*shortenv1.DeactivateURLRedirectionResponse, error) {
|
||||||
|
_, err := s.db.SetActive(ctx, database.SetActiveParams{
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
UrlID: req.UrlId,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Log(ctx, slog.LevelError, err.Error())
|
||||||
|
return &shortenv1.DeactivateURLRedirectionResponse{
|
||||||
|
Ok: false,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &shortenv1.DeactivateURLRedirectionResponse{
|
||||||
|
Ok: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteURLRedirection implements [shortenv1connect.ShortenServiceHandler].
|
||||||
|
func (s *ShortenerService) DeleteURLRedirection(ctx context.Context, req *shortenv1.DeleteURLRedirectionRequest) (*shortenv1.DeleteURLRedirectionResponse, error) {
|
||||||
|
_, err := s.db.DeleteURL(ctx, req.UrlId)
|
||||||
|
if err != nil {
|
||||||
|
slog.Log(ctx, slog.LevelError, err.Error())
|
||||||
|
return &shortenv1.DeleteURLRedirectionResponse{
|
||||||
|
Ok: false,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
return &shortenv1.DeleteURLRedirectionResponse{
|
||||||
|
Ok: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListURLRedirections implements [shortenv1connect.ShortenServiceHandler].
|
||||||
|
func (s *ShortenerService) ListURLRedirections(ctx context.Context, req *shortenv1.ListURLRedirectionsRequest) (*shortenv1.ListURLRedirectionsResponse, error) {
|
||||||
|
urls, err := s.db.GetURLs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slog.Log(ctx, slog.LevelError, err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var redirections []*shortenv1.URLRedirection
|
||||||
|
for _, url := range urls {
|
||||||
|
redirections = append(redirections, &shortenv1.URLRedirection{
|
||||||
|
UrlId: int32(url.UrlID),
|
||||||
|
ShortCode: url.ShortCode,
|
||||||
|
Url: url.LongUrl,
|
||||||
|
CreatedAt: timestamppb.New(url.CreatedAt),
|
||||||
|
ExpiresAt: timestamppb.New(url.ExpiresAt.Time),
|
||||||
|
IsActive: url.IsActive,
|
||||||
|
ClickCountByDay: []int32{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &shortenv1.ListURLRedirectionsResponse{
|
||||||
|
UrlRedirections: redirections,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewShortenerService(conf *config.Config, db *database.Queries) shortenv1connect.ShortenServiceHandler {
|
||||||
|
return &ShortenerService{
|
||||||
|
conf: conf,
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
101
internal/telemetry/telemetry.go
Normal file
101
internal/telemetry/telemetry.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package telemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||||
|
"go.opentelemetry.io/otel/log/global"
|
||||||
|
sdklog "go.opentelemetry.io/otel/sdk/log"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
"go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newResource(serviceName string) *resource.Resource {
|
||||||
|
return resource.NewWithAttributes(
|
||||||
|
resource.Default().SchemaURL(),
|
||||||
|
attribute.String("service.name", serviceName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init sets up OpenTelemetry tracing, metrics, and logs via OTLP Push.
|
||||||
|
// It returns a shutdown function that should be deferred in main.
|
||||||
|
func Init(ctx context.Context, endpoint string, serviceName string) (func(context.Context) error, error) {
|
||||||
|
res := newResource(serviceName)
|
||||||
|
|
||||||
|
var shutdownFuncs []func(context.Context) error
|
||||||
|
shutdown := func(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
for _, fn := range shutdownFuncs {
|
||||||
|
if fnErr := fn(ctx); fnErr != nil {
|
||||||
|
err = errors.Join(err, fnErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var traceOpts []otlptracehttp.Option
|
||||||
|
var metricOpts []otlpmetrichttp.Option
|
||||||
|
var logOpts []otlploghttp.Option
|
||||||
|
|
||||||
|
if endpoint != "" {
|
||||||
|
endpoint = strings.TrimSuffix(endpoint, "/")
|
||||||
|
traceOpts = append(traceOpts, otlptracehttp.WithEndpointURL(endpoint+"/v1/traces"))
|
||||||
|
metricOpts = append(metricOpts, otlpmetrichttp.WithEndpointURL(endpoint+"/v1/metrics"))
|
||||||
|
logOpts = append(logOpts, otlploghttp.WithEndpointURL(endpoint+"/v1/logs"))
|
||||||
|
} else {
|
||||||
|
// Default to insecure localhost if no endpoint is specified
|
||||||
|
traceOpts = append(traceOpts, otlptracehttp.WithInsecure())
|
||||||
|
metricOpts = append(metricOpts, otlpmetrichttp.WithInsecure())
|
||||||
|
logOpts = append(logOpts, otlploghttp.WithInsecure())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace setup
|
||||||
|
traceExporter, err := otlptracehttp.New(ctx, traceOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return shutdown, err
|
||||||
|
}
|
||||||
|
tp := trace.NewTracerProvider(
|
||||||
|
trace.WithBatcher(traceExporter),
|
||||||
|
trace.WithResource(res),
|
||||||
|
)
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
shutdownFuncs = append(shutdownFuncs, tp.Shutdown)
|
||||||
|
|
||||||
|
// Metrics setup
|
||||||
|
metricExporter, err := otlpmetrichttp.New(ctx, metricOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return shutdown, err
|
||||||
|
}
|
||||||
|
mp := metric.NewMeterProvider(
|
||||||
|
metric.WithReader(metric.NewPeriodicReader(metricExporter)),
|
||||||
|
metric.WithResource(res),
|
||||||
|
)
|
||||||
|
otel.SetMeterProvider(mp)
|
||||||
|
shutdownFuncs = append(shutdownFuncs, mp.Shutdown)
|
||||||
|
|
||||||
|
// Logs setup
|
||||||
|
logExporter, err := otlploghttp.New(ctx, logOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return shutdown, err
|
||||||
|
}
|
||||||
|
lp := sdklog.NewLoggerProvider(
|
||||||
|
sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)),
|
||||||
|
sdklog.WithResource(res),
|
||||||
|
)
|
||||||
|
global.SetLoggerProvider(lp)
|
||||||
|
shutdownFuncs = append(shutdownFuncs, lp.Shutdown)
|
||||||
|
|
||||||
|
// Route slog to OpenTelemetry
|
||||||
|
slog.SetDefault(otelslog.NewLogger(serviceName))
|
||||||
|
|
||||||
|
return shutdown, nil
|
||||||
|
}
|
||||||
12
k6.js
Normal file
12
k6.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import http from "k6/http";
|
||||||
|
import { sleep } from "k6";
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
iterations: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
http.get("http://localhost:3001/abcdefghij");
|
||||||
|
|
||||||
|
sleep(0.2);
|
||||||
|
}
|
||||||
86
main.go
Normal file
86
main.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"connectrpc.com/grpcreflect"
|
||||||
|
"connectrpc.com/validate"
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/config"
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/database"
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/proto/shorten/v1/shortenv1connect"
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/service"
|
||||||
|
"git.kocoder.xyz/vt/shortener/internal/telemetry"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"github.com/uptrace/opentelemetry-go-extra/otelsql"
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Fallback logger for early startup errors
|
||||||
|
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
||||||
|
|
||||||
|
conf := config.Read()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
shutdown, err := telemetry.Init(ctx, conf.OTLP_ENDPOINT, "shortener-server")
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to initialize telemetry", slog.String("err", err.Error()))
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := shutdown(ctx); err != nil {
|
||||||
|
slog.Error("Error shutting down telemetry", slog.String("err", err.Error()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Connect to DB and wrap with otelsql for database tracing
|
||||||
|
db, err := otelsql.Open("postgres", conf.DB_URL)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to connect to database", slog.String("err", err.Error()))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
otelsql.ReportDBStatsMetrics(db, otelsql.WithAttributes(
|
||||||
|
attribute.String("db.system", "postgresql"),
|
||||||
|
))
|
||||||
|
|
||||||
|
dbQueries := database.New(db)
|
||||||
|
|
||||||
|
srv := service.NewServer(conf, dbQueries)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
path, handler := shortenv1connect.NewShortenServiceHandler(
|
||||||
|
service.NewShortenerService(conf, dbQueries),
|
||||||
|
connect.WithInterceptors(validate.NewInterceptor()),
|
||||||
|
)
|
||||||
|
mux.Handle(path, handler)
|
||||||
|
|
||||||
|
reflector := grpcreflect.NewStaticReflector(shortenv1connect.ShortenServiceName)
|
||||||
|
mux.Handle(grpcreflect.NewHandlerV1(reflector))
|
||||||
|
mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector))
|
||||||
|
|
||||||
|
mux.Handle("/", srv)
|
||||||
|
|
||||||
|
// Wrap the server with OpenTelemetry HTTP handler for tracing & metrics
|
||||||
|
instrumentedHandler := otelhttp.NewHandler(mux, "shortener-server2")
|
||||||
|
|
||||||
|
p := new(http.Protocols)
|
||||||
|
p.SetHTTP1(true)
|
||||||
|
// Use h2c so we can serve HTTP/2 without TLS.
|
||||||
|
p.SetUnencryptedHTTP2(true)
|
||||||
|
s := http.Server{
|
||||||
|
Addr: ":3001",
|
||||||
|
Handler: instrumentedHandler,
|
||||||
|
Protocols: p,
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Starting server on :3001")
|
||||||
|
if err := s.ListenAndServe(); err != nil {
|
||||||
|
slog.Error("Server failed", slog.String("err", err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user