Skip to content

Commit

Permalink
Add quick'n'dirty packages listing
Browse files Browse the repository at this point in the history
  • Loading branch information
ghivert committed Oct 16, 2024
1 parent c5a5a8b commit e28cef7
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 7 deletions.
42 changes: 42 additions & 0 deletions apps/backend/src/backend/postgres/queries.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,48 @@ pub fn select_package_by_popularity(db: pgo.Connection, page: Int) {
|> result.map_error(error.DatabaseError)
}

pub fn select_package_by_updated_at(db: pgo.Connection) {
"SELECT
name,
repository,
documentation,
hex_url,
licenses,
description,
rank,
popularity
FROM package
ORDER BY updated_at DESC"
|> pgo.execute(
db,
[],
dynamic.decode8(
fn(a, b, c, d, e, f, g, h) {
json.object([
#("name", json.string(a)),
#("repository", json.nullable(b, json.string)),
#("documentation", json.nullable(c, json.string)),
#("hex-url", json.nullable(d, json.string)),
#("licenses", json.string(e)),
#("description", json.nullable(f, json.string)),
#("rank", json.int(g)),
#("popularity", json.nullable(h, json.string)),
])
},
dynamic.element(0, dynamic.string),
dynamic.element(1, dynamic.optional(dynamic.string)),
dynamic.element(2, dynamic.optional(dynamic.string)),
dynamic.element(3, dynamic.optional(dynamic.string)),
dynamic.element(4, dynamic.string),
dynamic.element(5, dynamic.optional(dynamic.string)),
dynamic.element(6, dynamic.int),
dynamic.element(7, dynamic.optional(dynamic.string)),
),
)
|> result.map(fn(r) { r.rows })
|> result.map_error(error.DatabaseError)
}

pub fn insert_analytics(
db: pgo.Connection,
id: Int,
Expand Down
6 changes: 6 additions & 0 deletions apps/backend/src/backend/router.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ fn encode_package(package: #(String, String, Int, option.Option(Int))) {
pub fn handle_get(req: Request, ctx: Context) {
case wisp.path_segments(req) {
["healthcheck"] -> wisp.ok()
["packages"] ->
queries.select_package_by_updated_at(ctx.db)
|> result.unwrap([])
|> json.preprocessed_array
|> json.to_string_builder
|> wisp.json_response(200)
["trendings"] ->
wisp.get_query(req)
|> list.find(fn(item) { item.0 == "page" })
Expand Down
3 changes: 3 additions & 0 deletions apps/frontend/src/data/model.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub type Model {
search_results: Dict(String, SearchResults),
index: Index,
loading: Bool,
packages: List(package.Package),
view_cache: Dict(String, Element(Msg)),
route: router.Route,
is_mobile: Bool,
Expand Down Expand Up @@ -57,6 +58,7 @@ pub fn init() {
search_results: dict.new(),
index: index,
loading: False,
packages: [],
view_cache: dict.new(),
route: router.Home,
is_mobile: is_mobile(),
Expand Down Expand Up @@ -338,6 +340,7 @@ pub fn reset(model: Model) {
index: [],
loading: False,
view_cache: model.view_cache,
packages: model.packages,
route: router.Home,
is_mobile: is_mobile(),
trendings: model.trendings,
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/data/msg.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub type Analytics {

pub type Msg {
None
Packages(packages: Result(List(package.Package), http.HttpError))
OnSearchFocus(event: Event)
SubmitSearch
UpdateIsMobile(is_mobile: Bool)
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/src/data/package.gleam
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import gleam/dynamic
import gleam/io
import gleam/json
import gleam/option.{type Option}
import gleam/result
Expand Down Expand Up @@ -34,7 +35,8 @@ pub fn decoder(dyn) {
dynamic.field("popularity", fn(dyn) {
use data <- result.try(dynamic.optional(dynamic.string)(dyn))
option.unwrap(data, "{}")
|> json.decode(using: dynamic.field("github", dynamic.int))
|> json.decode(using: dynamic.optional_field("github", dynamic.int))
|> result.map(option.unwrap(_, 0))
|> result.replace_error([dynamic.DecodeError("", "", [])])
}),
)(dyn)
Expand Down
10 changes: 9 additions & 1 deletion apps/frontend/src/frontend.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import frontend/view/body/search_result as sr
import gleam/bool
import gleam/dict
import gleam/dynamic
import gleam/io
import gleam/option.{None, Some}
import gleam/pair
import gleam/result
Expand Down Expand Up @@ -99,6 +100,10 @@ fn init(_) {
http.expect_json(dynamic.list(package.decoder), msg.Trendings)
|> http.get(config.api_endpoint() <> "/trendings", _),
)
|> update.add_effect(
http.expect_json(dynamic.list(package.decoder), msg.Packages)
|> http.get(config.api_endpoint() <> "/packages", _),
)
|> update.add_effect(
msg.OnAnalytics
|> http.expect_json(
Expand Down Expand Up @@ -135,11 +140,13 @@ fn on_url_change(uri: Uri) -> Msg {
}

fn update(model: Model, msg: Msg) {
case msg {
case io.debug(msg) {
msg.UpdateInput(content) -> update_input(model, content)
msg.SubmitSearch -> submit_search(model)
msg.Reset -> reset(model)
msg.None -> update.none(model)
msg.Packages(Ok(packages)) -> model.Model(..model, packages:) |> update.none
msg.Packages(_) -> update.none(model)
msg.ScrollTo(id) -> scroll_to(model, id)
msg.OnRouteChange(route) -> handle_route_change(model, route)
msg.Trendings(trendings) -> handle_trendings(model, trendings)
Expand Down Expand Up @@ -242,6 +249,7 @@ fn handle_route_change(model: Model, route: router.Route) {
let model = model.update_route(model, route)
case route {
router.Home -> model.update_input(model, "")
router.Packages -> model.update_input(model, "")
router.Trending -> model.update_input(model, "")
router.Analytics -> model.update_input(model, "")
router.Search(q) ->
Expand Down
3 changes: 3 additions & 0 deletions apps/frontend/src/frontend/router.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import lustre/effect
pub type Route {
Home
Search(query: String)
Packages
Trending
Analytics
}

pub fn parse_uri(uri: Uri) -> Route {
case uri.path_segments(uri.path) {
["search"] -> handle_search_path(uri)
["packages"] -> Packages
["trending"] -> Trending
["analytics"] -> Analytics
_ -> Home
Expand All @@ -38,6 +40,7 @@ pub fn update_page_title(route: Route) {
use _ <- effect.from()
case route {
Home -> ffi.update_title("Gloogle")
Packages -> ffi.update_title("Gloogle — Packages")
Search(q) -> ffi.update_title("Gloogle — Search " <> q)
Trending -> ffi.update_title("Gloogle — Trending")
Analytics -> ffi.update_title("Gloogle — Analytics")
Expand Down
57 changes: 57 additions & 0 deletions apps/frontend/src/frontend/view/body/body.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ fn sidebar(model: Model) {
h.div([a.class("sidebar-spacer"), disabled], []),
h.div([a.class("sidebar-links")], [
sidebar_link(href: "/analytics", icon: icons.trends(), title: "Analytics"),
sidebar_link(href: "/packages", icon: icons.gift(), title: "Packages"),
// s.sidebar_link_wrapper([], [
// s.sidebar_icon([], [icons.shortcuts()]),
// s.sidebar_link([], [el.text("Shortcuts")]),
Expand Down Expand Up @@ -373,9 +374,65 @@ fn view_analytics(model: Model) {
])
}

fn view_packages(model: Model) {
el.fragment([
sidebar(model),
h.main(
[a.class("main"), a.style([#("padding", "24px 36px")])],
list.intersperse(
{
use package <- list.map(model.packages)
h.div([a.class("search-result")], [
h.div([a.class("search-details")], [
h.div([a.class("search-details-name")], [h.text(package.name)]),
h.div([a.class("search-details-links")], [
case package.hex_url {
option.None -> el.none()
option.Some(url) ->
h.a([a.href(url), a.target("_blank"), a.rel("noreferrer")], [
h.text("Show hex"),
])
},
case package.documentation {
option.None -> el.none()
option.Some(url) ->
h.a([a.href(url), a.target("_blank"), a.rel("noreferrer")], [
h.text("Show documentation"),
])
},
]),
]),
case package.description {
option.None -> el.none()
option.Some(description) ->
h.div([a.class("search-body")], [
h.div([a.class("signature")], [h.text(description)]),
])
},
h.div([a.class("search-details-links")], [
case package.repository {
option.None -> el.none()
option.Some(url) ->
h.a([a.href(url), a.target("_blank"), a.rel("noreferrer")], [
h.text("Show repository"),
])
},
h.div([a.class("search-details-licences")], [
package.licenses |> string.join(" ") |> h.text,
]),
]),
])
},
h.div([a.class("search-result-separator")], []),
),
),
])
}

pub fn body(model: Model) {
case model.route {
router.Home -> h.main([a.class("main")], [view_search_input(model)])
router.Packages -> view_packages(model)
router.Trending -> h.main([a.class("main")], [view_trending(model)])
router.Analytics -> view_analytics(model)
router.Search(_) -> {
Expand Down
7 changes: 2 additions & 5 deletions apps/frontend/src/frontend/view/navbar/navbar.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import lustre/element/html as h

fn navbar_links() {
s.nav_links([], [
s.trending([], [
h.text("Packages"),
s.coming_soon([], [h.text(" (coming soon…)")]),
]),
s.nav_link([a.href("/packages")], [h.text("Packages")]),
s.nav_link([a.href("/analytics")], [h.text("Analytics")]),
])
}
Expand All @@ -19,7 +16,7 @@ pub fn navbar(model: Model) {
s.navbar(transparent, [a.class("navbar")], [
case model.route {
router.Home -> navbar_links()
router.Search(_) | router.Trending | router.Analytics ->
router.Search(_) | router.Trending | router.Analytics | router.Packages ->
s.navbar_search([], [
s.navbar_search_title([a.href("/")], [
s.search_lucy(40, [a.src("/images/lucy.svg")]),
Expand Down
10 changes: 10 additions & 0 deletions apps/frontend/src/stylesheets/all.css
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,13 @@ lazy-node:has(:not(:defined)) {
overflow: hidden;
padding: 12px;
}

.search-details-links {
display: flex;
gap: 12px;
font-size: 0.9rem;
}

.search-details-links a {
color: var(--input-text-color);
}

0 comments on commit e28cef7

Please sign in to comment.