Skip to content

Commit ed152ae

Browse files
committed
add new endpoint
1 parent c712fe7 commit ed152ae

File tree

5 files changed

+498
-417
lines changed

5 files changed

+498
-417
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
run:
2+
export GO_SHORT_KEY=abc && go run *.go --port=8020
3+
build:
4+
go build *.go

api.rest

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,11 @@ X-API-Key: abc
1717

1818
DELETE http://localhost:8020/api/a/f74f9
1919
Content-Type: application/json
20+
X-API-Key: abc
21+
22+
23+
### GET BY ID
24+
25+
GET http://localhost:8020/api/a/613f5
26+
Content-Type: application/json
2027
X-API-Key: abc

controller.go

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"path/filepath"
10+
"strconv"
11+
"time"
12+
13+
"github.com/gorilla/mux"
14+
)
15+
16+
func getURL(w http.ResponseWriter, r *http.Request) {
17+
// Extract space and ID parameters from the URL path
18+
vars := mux.Vars(r)
19+
space := vars["space"]
20+
id := vars["id"]
21+
22+
// Reinitialize the database for the specified space
23+
outputDir := getOutputDirFromArgs()
24+
dbFile := filepath.Join(outputDir, space+".db")
25+
26+
// Close the existing database connection if it's open
27+
if db != nil {
28+
db.Close()
29+
}
30+
31+
// Reinitialize the SQLite database with the specified space
32+
initDB(dbFile)
33+
34+
// Query the database for the short URL
35+
row := db.QueryRow(`
36+
SELECT id, url, meta, visited, last_visited_at, created_at, updated_at
37+
FROM short_urls
38+
WHERE id = ?
39+
`, id)
40+
41+
// Parse the query result
42+
var shortURL ShortURL
43+
var lastVisitedAt sql.NullTime
44+
err := row.Scan(
45+
&shortURL.ID,
46+
&shortURL.URL,
47+
&shortURL.Meta,
48+
&shortURL.Visited,
49+
&lastVisitedAt,
50+
&shortURL.CreatedAt,
51+
&shortURL.UpdatedAt,
52+
)
53+
if err != nil {
54+
http.Error(w, "Short URL not found", http.StatusNotFound)
55+
return
56+
}
57+
58+
// Assign the value to shortURL.LastVisitedAt only if it's not NULL
59+
if lastVisitedAt.Valid {
60+
shortURL.LastVisitedAt = lastVisitedAt.Time
61+
}
62+
63+
// Respond with the short URL and scraped metadata (json)
64+
response := JSONResponse{Status: "success", Message: "Short URL found successfully", Data: &shortURL}
65+
respondJSON(w, http.StatusOK, response)
66+
}
67+
68+
func getURLs(w http.ResponseWriter, r *http.Request) {
69+
// Extract space parameter from the URL path
70+
vars := mux.Vars(r)
71+
space := vars["space"]
72+
73+
// Reinitialize the database for the specified space
74+
outputDir := getOutputDirFromArgs()
75+
dbFile := filepath.Join(outputDir, space+".db")
76+
77+
// Close the existing database connection if it's open
78+
if db != nil {
79+
db.Close()
80+
}
81+
82+
// Reinitialize the SQLite database with the specified space
83+
initDB(dbFile)
84+
85+
// Parse query parameters for pagination
86+
page, err := strconv.Atoi(r.URL.Query().Get("page"))
87+
if err != nil || page < 1 {
88+
page = 1
89+
}
90+
91+
perPage, err := strconv.Atoi(r.URL.Query().Get("perPage"))
92+
if err != nil || perPage < 1 {
93+
perPage = 10
94+
}
95+
96+
// Calculate the offset based on the page and perPage values
97+
offset := (page - 1) * perPage
98+
99+
// Query the database to get paginated URLs
100+
rows, err := db.Query(`
101+
SELECT id, url, meta, visited, last_visited_at, created_at, updated_at
102+
FROM short_urls
103+
ORDER BY created_at DESC
104+
LIMIT ? OFFSET ?
105+
`, perPage, offset)
106+
if err != nil {
107+
log.Println("Error querying short URLs:", err)
108+
http.Error(w, "Failed to get short URLs", http.StatusInternalServerError)
109+
return
110+
}
111+
defer rows.Close()
112+
113+
// Iterate over the rows and build the response
114+
var shortURLs []ShortURL
115+
for rows.Next() {
116+
var shortURL ShortURL
117+
var lastVisitedAt sql.NullTime
118+
119+
err := rows.Scan(
120+
&shortURL.ID,
121+
&shortURL.URL,
122+
&shortURL.Meta,
123+
&shortURL.Visited,
124+
&lastVisitedAt,
125+
&shortURL.CreatedAt,
126+
&shortURL.UpdatedAt,
127+
)
128+
if err != nil {
129+
log.Println("Error scanning short URL row:", err)
130+
http.Error(w, "Failed to get short URLs", http.StatusInternalServerError)
131+
return
132+
}
133+
134+
// Assign the value to shortURL.LastVisitedAt only if it's not NULL
135+
if lastVisitedAt.Valid {
136+
shortURL.LastVisitedAt = lastVisitedAt.Time
137+
}
138+
139+
shortURL.Path = "/" + space + "/" + shortURL.ID
140+
shortURLs = append(shortURLs, shortURL)
141+
}
142+
143+
// Respond with the paginated short URLs
144+
responseJSON, err := json.Marshal(shortURLs)
145+
if err != nil {
146+
log.Println("Error encoding short URLs to JSON:", err)
147+
http.Error(w, "Failed to encode short URLs", http.StatusInternalServerError)
148+
return
149+
}
150+
151+
/* if responseJSON is null then response array is empty */
152+
if len(shortURLs) == 0 {
153+
responseJSON = []byte("[]")
154+
}
155+
156+
w.Header().Set("Content-Type", "application/json")
157+
w.Write(responseJSON)
158+
}
159+
160+
func deleteURL(w http.ResponseWriter, r *http.Request) {
161+
// Extract space and ID parameters from the URL path
162+
vars := mux.Vars(r)
163+
space := vars["space"]
164+
id := vars["id"]
165+
166+
// Reinitialize the database for the specified space
167+
outputDir := getOutputDirFromArgs()
168+
dbFile := filepath.Join(outputDir, space+".db")
169+
170+
// Close the existing database connection if it's open
171+
if db != nil {
172+
db.Close()
173+
}
174+
175+
// Reinitialize the SQLite database with the specified space
176+
initDB(dbFile)
177+
178+
// Check if the URL with the given ID exists
179+
if !idExists(id) {
180+
181+
// Respond with a success message
182+
response := JSONResponse{Status: "error", Message: "URL not found"}
183+
respondJSON(w, http.StatusNotFound, response)
184+
return
185+
}
186+
187+
// Delete the URL from the database
188+
_, err := db.Exec("DELETE FROM short_urls WHERE id = ?", id)
189+
if err != nil {
190+
log.Println("Error deleting short URL:", err)
191+
http.Error(w, "Failed to delete short URL", http.StatusInternalServerError)
192+
return
193+
}
194+
195+
196+
// Respond with a success message
197+
response := JSONResponse{Status: "success", Message: "URL deleted successfully"}
198+
respondJSON(w, http.StatusOK, response)
199+
}
200+
201+
202+
func redirectURL(w http.ResponseWriter, r *http.Request) {
203+
// Extract space and ID parameters from the URL path
204+
vars := mux.Vars(r)
205+
space := vars["space"]
206+
id := vars["id"]
207+
208+
// Reinitialize the database for the specified space
209+
outputDir := getOutputDirFromArgs()
210+
dbFile := filepath.Join(outputDir, space+".db")
211+
212+
// Close the existing database connection if it's open
213+
if db != nil {
214+
db.Close()
215+
}
216+
217+
// Reinitialize the SQLite database with the specified space
218+
initDB(dbFile)
219+
220+
// Get the long URL and meta information for the given ID and space
221+
var longURL string
222+
var meta string
223+
err := db.QueryRow("SELECT url, meta FROM short_urls WHERE id = ?", id).Scan(&longURL, &meta)
224+
if err != nil {
225+
http.Error(w, "URL not found", http.StatusNotFound)
226+
return
227+
}
228+
229+
// Increment the 'visited' count and update 'last_visited_at'
230+
_, err = db.Exec("UPDATE short_urls SET visited = visited + 1, last_visited_at = ? WHERE id = ?", time.Now(), id)
231+
if err != nil {
232+
log.Println("Error updating statistics:", err)
233+
// Continue with the redirection even if there's an error updating statistics
234+
}
235+
236+
// Redirect to the long URL
237+
http.Redirect(w, r, longURL, http.StatusFound)
238+
}
239+
240+
func createShortURLWithScrape(w http.ResponseWriter, r *http.Request) {
241+
// Initialize SQLite database with the current year's hash
242+
outputDir := getOutputDirFromArgs()
243+
currentYear := getHashedYear(time.Now().Year())
244+
dbFile := filepath.Join(outputDir, currentYear+".db")
245+
246+
// Close the existing database connection if it's open
247+
if db != nil {
248+
db.Close()
249+
}
250+
251+
// Initialize a new SQLite database with the current year's hash
252+
initDB(dbFile)
253+
254+
space := currentYear
255+
256+
// Parse the request body as JSON
257+
var requestData struct {
258+
URL string `json:"url"`
259+
CustomID string `json:"custom_id"`
260+
}
261+
262+
decoder := json.NewDecoder(r.Body)
263+
if err := decoder.Decode(&requestData); err != nil {
264+
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
265+
return
266+
}
267+
268+
// Validate input
269+
if requestData.URL == "" {
270+
http.Error(w, "URL is required", http.StatusBadRequest)
271+
return
272+
}
273+
274+
// Use custom ID if provided, generate a new one otherwise
275+
var id string
276+
if requestData.CustomID != "" {
277+
// Check if the custom ID is available in the database
278+
if idExists(requestData.CustomID) {
279+
http.Error(w, "Custom ID is already in use", http.StatusBadRequest)
280+
return
281+
}
282+
id = requestData.CustomID
283+
} else {
284+
// Generate a unique ID (hash of current time)
285+
id = generateUniqueID(space, requestData.URL)
286+
}
287+
288+
// Scrape metadata from the provided URL
289+
meta, err := scrapePageMetadata(requestData.URL)
290+
if err != nil {
291+
log.Println("Error scraping metadata:", err)
292+
}
293+
294+
fmt.Println("Scraped metadata:", meta)
295+
296+
// Convert the meta map to a MetaScanner
297+
metaScanner := ConvertPageMetadataToMetaScanner(meta)
298+
299+
// Convert the meta map to a JSON string
300+
metaJSON, err := json.Marshal(metaScanner)
301+
if err != nil {
302+
http.Error(w, "Failed to convert meta to JSON string", http.StatusInternalServerError)
303+
return
304+
}
305+
306+
// Insert the short URL into the database with the parsed meta values
307+
createdAt := time.Now()
308+
_, err = db.Exec(`
309+
INSERT INTO short_urls (id, url, meta, visited, last_visited_at, created_at, updated_at)
310+
VALUES (?, ?, ?, 0, NULL, ?, ?)
311+
`, id, requestData.URL, string(metaJSON), createdAt, createdAt)
312+
313+
if err != nil {
314+
log.Println("Error inserting short URL into the database:", err)
315+
http.Error(w, "Failed to create short URL", http.StatusInternalServerError)
316+
return
317+
}
318+
319+
// Respond with the short URL and scraped metadata (json)
320+
data := ShortURL{
321+
ID: id,
322+
URL: requestData.URL,
323+
Meta: metaScanner, // Use the converted MetaScanner
324+
Path: "/" + space + "/" + id,
325+
LastVisitedAt: time.Time{},
326+
CreatedAt: createdAt,
327+
UpdatedAt: createdAt,
328+
}
329+
330+
response := JSONResponse{Status: "success", Message: "Short URL created successfully", Data: &data}
331+
respondJSON(w, http.StatusCreated, response)
332+
}

0 commit comments

Comments
 (0)