1// SPDX-FileCopyrightText: 2022 Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5package main
6
7import (
8 "errors"
9 "fmt"
10 "log"
11 "strings"
12 "text/template"
13
14 "github.com/dgraph-io/badger/v3"
15)
16
17// Create shortens a given URL with an optional name. If a name is provided,
18// that name will be used. Otherwise, 4-character string will be generated and
19// used instead.
20func (m model) create(name string, url string) string {
21 err := m.database.Update(func(txn *badger.Txn) error {
22 return txn.Set([]byte(name), []byte(url))
23 })
24 if err != nil {
25 // TODO: return an error instead so it can be handled like an error
26 return fmt.Sprint(err)
27 }
28
29 log.Println("\"" + url + "\" mapped to \"" + name + "\"")
30 return fmt.Sprint("URL mapped to ", name, "\n")
31}
32
33// read() accepts a start variable for the first link in a set (implying reverse
34// iteration), an end variable for the last link in a set (implying forward
35// iteration), and a count variable for the number of requested entries. The
36// requested set is returned as JSON.
37func (m model) read(start string, end string, count int) (map[string]string, error) {
38 links := make(map[string]string)
39
40 if !(count > 0) {
41 return nil, errors.New("Count parameter is required")
42 }
43
44 err := m.database.View(func(txn *badger.Txn) error {
45 opts := badger.DefaultIteratorOptions
46 opts.PrefetchSize = 20
47 if start != "" && end == "" {
48 // Start value provided, iterate backwards
49 opts.Reverse = true
50 }
51 iterator := txn.NewIterator(opts)
52 defer iterator.Close()
53 if start == "" && end == "" {
54 iterator.Rewind()
55 } else if start != "" && end == "" {
56 // Set position to "start"
57 iterator.Seek([]byte(start))
58 // If "start" exists, move to next value
59 if iterator.Valid() {
60 iterator.Next()
61 }
62 } else if start == "" && end != "" {
63 // Start value provided, iterate forwards
64 // Set position to "end"
65 iterator.Seek([]byte(end))
66 // If "end" exists, move to next value
67 if iterator.Valid() {
68 iterator.Next()
69 }
70 } else {
71 return errors.New("Only provide start OR end parameters, not both")
72 }
73 for i := 0; i < count; i++ {
74 if iterator.Valid() {
75 err := iterator.Item().Value(func(v []byte) error {
76 links[string(iterator.Item().Key())] = string(v)
77 return nil
78 })
79 if err != nil {
80 return err
81 }
82 iterator.Next()
83 }
84 }
85 return nil
86 })
87 if err != nil {
88 return nil, err
89 }
90
91 return links, nil
92}
93
94// Update modifies a shortened link
95func (m model) update(name string, url string, oldName string) string {
96 m.create(name, url)
97 if name != oldName {
98 m.delete(oldName)
99 }
100 return fmt.Sprint("\"" + url + "\" mapped to \"" + name + "\"")
101}
102
103// Delete removes a shortened URL from the database given its name.
104func (m model) delete(name string) string {
105 err := m.database.Update(func(txn *badger.Txn) error {
106 return txn.Delete([]byte(name))
107 })
108 if err != nil {
109 return fmt.Sprint(err)
110 }
111
112 log.Println("\"" + name + "\" has been deleted")
113 return fmt.Sprint("\"" + name + "\" has been deleted\n")
114}
115
116// nameExists returns true if the provided name is already stored in the
117// database and false if it's not.
118func (m model) nameExists(name string) bool {
119 err := m.database.View(func(txn *badger.Txn) error {
120 _, err := txn.Get([]byte(name))
121 if err != nil {
122 return err
123 }
124 return nil
125 })
126 return err == nil
127}
128
129// unauthenticated serves the unauthenticated home page to the visitor
130func unauthenticated() string {
131 home, err := templates.ReadFile("templates/home_unauthenticated.html")
132 if err != nil {
133 log.Fatalln(err)
134 }
135 return string(home)
136}
137
138// authenticated serves the authenticated dashboard to the user
139func (m model) authenticated() string {
140 dash, err := templates.ReadFile("templates/home_authenticated.html")
141 if err != nil {
142 log.Fatalln(err)
143 }
144 tmpl, err := template.New("authenticated").Parse(string(dash))
145 if err != nil {
146 log.Println(err)
147 }
148 page := new(strings.Builder)
149 err = tmpl.Execute(page, m.genTable())
150 if err != nil {
151 log.Println(err)
152 }
153 return page.String()
154}
155
156type EditFields struct {
157 URL string
158 Name string
159}
160
161// update serves the editing interface to the user
162func (m model) edit(name string, url string) string {
163 fields := EditFields{url, name}
164 editPage, err := templates.ReadFile("templates/edit.html")
165 if err != nil {
166 log.Fatalln(err)
167 }
168 tmpl, err := template.New("editPage").Parse(string(editPage))
169 if err != nil {
170 log.Println(err)
171 }
172 page := new(strings.Builder)
173 err = tmpl.Execute(page, fields)
174 if err != nil {
175 log.Println(err)
176 }
177 return page.String()
178}