From 649f353b8c5980ebe90eb2e049bbc1588dbb37a1 Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Mon, 26 Apr 2021 20:49:46 -0500 Subject: [PATCH] Improve post query performance --- oldstaticpages/TODO.txt | 1 + oldstaticpages/about.txt | 50 +++++ oldstaticpages/contact.txt | 16 ++ oldstaticpages/guidelines.txt | 26 +++ oldstaticpages/ircrules.txt | 23 ++ oldstaticpages/manifesto.txt | 20 ++ oldstaticpages/monthlyupdatepolicy.txt | 98 +++++++++ oldstaticpages/press.txt | 21 ++ oldstaticpages/projectguidelines.txt | 199 ++++++++++++++++++ resetdb.sh | 8 +- src/migration/migration.go | 9 +- src/migration/migrationTemplate.txt | 1 + .../2021-04-26T000000Z_DeleteStaticPages.go | 90 ++++++++ .../2021-04-26T000001Z_FixPostCategoryId.go | 53 +++++ ...21-04-26T235720Z_AddCommonFieldsToPosts.go | 124 +++++++++++ src/models/post.go | 5 +- src/templates/src/index.html | 8 +- src/templates/urls.go | 4 +- src/website/feed.go | 30 ++- src/website/landing.go | 35 ++- 20 files changed, 771 insertions(+), 50 deletions(-) create mode 100644 oldstaticpages/TODO.txt create mode 100644 oldstaticpages/about.txt create mode 100644 oldstaticpages/contact.txt create mode 100644 oldstaticpages/guidelines.txt create mode 100644 oldstaticpages/ircrules.txt create mode 100644 oldstaticpages/manifesto.txt create mode 100644 oldstaticpages/monthlyupdatepolicy.txt create mode 100644 oldstaticpages/press.txt create mode 100644 oldstaticpages/projectguidelines.txt create mode 100644 src/migration/migrations/2021-04-26T000000Z_DeleteStaticPages.go create mode 100644 src/migration/migrations/2021-04-26T000001Z_FixPostCategoryId.go create mode 100644 src/migration/migrations/2021-04-26T235720Z_AddCommonFieldsToPosts.go diff --git a/oldstaticpages/TODO.txt b/oldstaticpages/TODO.txt new file mode 100644 index 00000000..b1370a4a --- /dev/null +++ b/oldstaticpages/TODO.txt @@ -0,0 +1 @@ +// TODO: Delete this folder! \ No newline at end of file diff --git a/oldstaticpages/about.txt b/oldstaticpages/about.txt new file mode 100644 index 00000000..f61722ec --- /dev/null +++ b/oldstaticpages/about.txt @@ -0,0 +1,50 @@ +

+ We're a small group of programmers and idealists who are trying to make +programming better for everyone. And we think the best way to do that is by +collecting the best, down-to-earth programming projects in one place and +building a community of the most talented programmers and most eager learners we +can find. +

+

Abner Coimbre

+

+Abner Coimbre started Handmade Network when rallying the Handmade Hero community around a common goal. He leads the policies, initiatives, and public facing of the network. Abner became NASA's Kennedy Intern of the Year in 2015 and was named to Kennedy’s 2016 Top 10 Innovators. In 2017 Abner moved to Seattle, WA to work for Jonathan Blow's studio Thekla Inc. +

+

Twitter: @AbnerCoimbre

+
+

Jeroen van Rijn

+

+Jeroen is just this guy, you know. He's a veteran programmer who at one time found himself chin deep in webdev and +decided the best thing for his sanity was to retire from it. Then Abner proposed what would become Handmade Network and +he decided to dust off his keyboard and join the fray, only to find the keyboard wasn't particularly dusty yet. Some +keys were getting stuck or unresponsive, sure, but a replacement keyboard soon fixed that. When not wrangling RSI, he +develops the backend software that makes the website tick.

Apart from this he has a keen interest in low-level +programming, optimisation, compiler writing and games development, among other subjects. When the site software is +sufficiently mature, he plans to do an educational series on the Handmade Network, passing on some of the 30 years of +collected knowledge he's gathered. +

+

Twitter: @J_vanRijn

+
+

Matt Mascarenhas

+

+Matt is a lifelong computer user, music player and book reader. In contrast to everyone else on the Handmade Network +team, his education only touched on computer programming, having culminated with a degree in English language and +literature. Or, rather, it would have culminated there had Casey not begun his educational programming project Handmade +Hero. +

+Within the Handmade Network, Matt has become the person to ask if you want to know if / when Casey covered a particular +topic on Handmade Hero – at least until the annotation system receives its long-expected overhaul, at which point he'll +become partially redundant – and will be the first port of call should you have any issues with the Handmade Network +website. +

+While not annotating or grepping the annotations for answers to people's questions, Matt practises programming in C with +a view to developing a bunch of software – to play, to run your console-based applications, for doing linguistic +research and for staging theatrical productions – writes audio and story for games, and evangelises his Arch Linux +system, taking full credit for Abner's discovery of the beautiful distribution. +

+

Twitter: @miblo_

+
+

Andrew Chronister

+

+Andrew is co-developer of Handmade Network and a student of computer engineering at the University of Washington. When he's not coding away at his project of the month, he can be found doing pointless math and cracking bad jokes on niche experimental decentralized social networks. +

+

Mastodon: @chr@cybre.space

diff --git a/oldstaticpages/contact.txt b/oldstaticpages/contact.txt new file mode 100644 index 00000000..5f2f7c05 --- /dev/null +++ b/oldstaticpages/contact.txt @@ -0,0 +1,16 @@ +

If you are experiencing technical issues with the site, please send a detailed email to +team@handmadedev.org +with a description of the problem, and we'll do our best to address it in a +timely manner. +

+ +

For administrative issues, such as moderation disputes or content +revisions, contact any of the staff by email: +

+ \ No newline at end of file diff --git a/oldstaticpages/guidelines.txt b/oldstaticpages/guidelines.txt new file mode 100644 index 00000000..b8352ea9 --- /dev/null +++ b/oldstaticpages/guidelines.txt @@ -0,0 +1,26 @@ + +

+ The Handmade community strives to create an environment conducive to + innovation, education, and constructive discussion. To that end, we expect + members of the site to respect a basic set of principles to maintain civil + discourse and create an inclusive environment. It is up to the discretion + of the moderators to determine when these guidelines are being met, and + they are permitted to act accordingly. If you feel an action has been made + against you unreasonably, please contact an admin + privately with your grievance and we will review the decision. +

+

+ Some loose principles to follow while on handmade.network: +

+

diff --git a/oldstaticpages/ircrules.txt b/oldstaticpages/ircrules.txt new file mode 100644 index 00000000..77a086a6 --- /dev/null +++ b/oldstaticpages/ircrules.txt @@ -0,0 +1,23 @@ + +

+ +The Handmade Network IRC and all channels hosted on it follow the same basic set of rules as +the rest of the site. You are still using Handmade Network-provided utilities and interacting with the same community, +even if you are not doing so through a web browser pointed at the site. It is up to the channel and server operators to +decide how to best maintain a civil and constructive chat environment, and they will act as they see fit to maintain it. + +

+

+ Additional notes: +

+

+ diff --git a/oldstaticpages/manifesto.txt b/oldstaticpages/manifesto.txt new file mode 100644 index 00000000..e6d7af81 --- /dev/null +++ b/oldstaticpages/manifesto.txt @@ -0,0 +1,20 @@ + +
+

Modern computer hardware is amazing. Manufacturers have orchestrated billions of pieces of silicon into terrifyingly complex and efficient structures that sweep electrons through innumerable tangled paths, branchings, and reunions with the sole purpose of performing computations at more than a billion times per second. This awe-inspiring piece of computational wizardry has at its disposal multiple billions of uniquely addressible silicon plates where it can store the results of millions of computations in an array of several vanishingly small chips. All of this hardware, though each component often sits no further than 7 or 8 centimeters away from the others, cycles so fast that the speed of light, a physical law of the universe, limits the rate at which they communicate with each other. +

So why is software still slow? +

Why does it take your operating system 10 seconds, 30 seconds, a minute to boot up? Why does your word processor freeze when you save a document on the cloud? Why does your web browser take 3, 4, 10 seconds to load a web page? Why does your phone struggle to keep more than a few apps open at a time? And why does each update somehow make the problem worse? +

We made it slow. +

Not necessarily you, not necessarily me, not necessarily any single person in particular. But we, the software development community, made it slow by ignoring the fundamental reality of our occupation. We write code, code that runs on computers. Real computers, with central processing units and random access memory and hard disk drives and display buffers. Real computers, with integer and bitwise math and floating point units and L2 caches, with threads and cores and a tenuous little network connection to a million billion other computers. Real computers not built for ease of human understanding but for blindingly, incomprehensibly fast speed. +

A lot of us have forgotten that. +

In our haste to get our products, our projects, the works of our hands and minds, to as many people as possible, we take shortcuts. We make assumptions. We generalize, and abstract, and assume that just because these problems have been solved before that they never need to be solved again. We build abstraction layers, then forget we built them and build more on top. +

And it's true that many of us think we do not have the time, the money, the mental bandwidth to always consider these things in detail. The deadline is approaching or the rent is due or we have taxes to fill out and a manager on our back and someone asking us why we always spend so much time at the office, and we just have to stick the library or virtual machine or garbage collector in there to cover up the places we can't think through right now. +

Others of us were never taught to think about the computer itself. We learned about objects and classes and templates and how to make our code clean and pretty. We learned how to write code to make the client or the manager or the teacher happy, but made the processor churn. And because we did, that amazing speed we'd been granted was wasted, by us, in a death by a thousand abstraction layers. +

But some of us aren't satisfied with that. +

Some of us take a few extra steps into the covered territory, the wheels sitting, motionless, in a pile behind us, examine their designs and decide there is a better way. The more experienced among us remember how software used to be, the potential that we know exists for computer programs to be useful, general, and efficient. Others of us got fed up with the tools we were expected to use without complaint, but which failed us time and time again. Some of us are just curious and don't know what's good for us. Don't trust what we've been told is good for us. +

We sat down and looked at our hardware, and examined our data, and thought about how to use the one to transform the other. We tinkered, and measured, and read, and compared, and wrote, and refined, and modified, and measured again, over and over, until we found we had built the same thing, but 10 times faster and incomparably more useful to the people we designed it for. And we had built it by hand. +

That is what Handmade means. It's not a technique or a language or a management strategy, it isn't a formula or a library or an abstraction. It's an idea. The idea that we can build software that works with the computer, not against it. The idea that sometimes an individual programmer can be more productive than a large team, that a small group can do more than an army of software engineers and *do it better*. The idea that programming is about transforming data and we wield the code, the tool we use to bend that data to our will. +

It doesn't require a degree, or a dissertation, or a decade of experience. You don't need an +expensive computer or a certificate or even prior knowledge. All you need is an open mind and a sense of +curiosity. We'll help you with the rest. +

Will you join us? +

Will you build your software by hand?

diff --git a/oldstaticpages/monthlyupdatepolicy.txt b/oldstaticpages/monthlyupdatepolicy.txt new file mode 100644 index 00000000..daf962ec --- /dev/null +++ b/oldstaticpages/monthlyupdatepolicy.txt @@ -0,0 +1,98 @@ +

The Handmade Network provides a place for project owners such as yourself +to showcase and record the development of their projects, and for +users to discover, follow and discuss these projects. This Monthly +Update Policy applies to your project if it doesn’t fit into a category listed under +“When the Monthly Update Policy Doesn’t Apply” below, and is +intended to encourage development and guard against your active or +complete project having to share web space with abandoned ones.

+

What Constitutes an Update

+

Outlined +are the ways a project owner, or a well-meaning member of the +project’s community, may contribute to a monthly update.

+

Blog Post

+

The most straightforward way to update the community with your progress +is with a simple blog post within the network. The length of the post +is not measured, as the network understands an individual may have +difficult months. Therefore, anything in the spectrum of a quick +message saying you had no progress, to a summary of a chat +discussion, all the way up to a full-blown detailed development log +would be considered a monthly update. Even with posts detailing your +inability to work for a month, looking back at your own development +history is an important exercise.

+

Forum Activity

+

Any activity in your project’s forum would satisfy the monthly update +policy. This includes contributions to any forum discussions from you +as the project owner and from members of the community.

+

Link to External Update

+

YouTube videos, GitHub / GitLab repository links (e.g. to a recent commit), +Reddit thread, Twitter threads discussing the project’s current +state, your own personal blog posts, are a few examples of what would +work here. Such updates must be linked somewhere in your project page, blog, or +forums in order to qualify. As a reminder, having +a hyperlink in to your external blog in your signature or profile +links that visitors are expected to follow up on without further +prompting from you doesn’t count.

+

When the Monthly Update Policy Doesn’t Apply

+

Grace Period

+

New projects are not required to provide updates until the month +following their approval. For example, if a project is posted in the +middle of March, they are not required to post any further updates +for March – the monthly update policy will go in effect for that +project in April.

+

Personal Circumstances

+

If the developer foresees not being able to provide a timely update this +month, or possibly even for a number of consecutive months, we invite +them to post a short update on their project blog letting their +audience know they’ll be taking a leave of absence. A reason need +not be supplied. Should you feel uncomfortable in doing so, an email +to webmaster@handmadedev.org +in private will let us flag the project as in hiatus and it will let +us deflect incoming questions about the project’s status in your +stead.

+

Although a blog post explaining the leave of absence will suffice, such an +e-mail is still appreciated, of course.

+

Project Completion

+

When the project owner is confident the project has entered a stage of +completion, a stage of maintenance with no further features, or it +has changed identity such that it is no longer considered the +original project, the monthly update would no longer be necessary. +Please advise us of this so that we may update the +status of your project, both for our bookkeeping, and for the +purposes of sorting projects by status.

+

What happens if the Project is Inactive

+

First Strike

+

If for any month there was no activity, without having notified the +network, the project owner will receive an e-mail within the first +week of the following month. No further action will be taken for that +month.

+

Second Strike and Beyond

+

If for any subsequent month there is no activity, without having notified +the network, the project will receive an e-mail within the first week +of the following month and will be flagged as ‘in-hiatus’ until +activity resumes. This will always happen for any inactive month +following the first strike of the year. At the start of a new year, +the strikes are reset, applying the first one first.

+

The project, while in hiatus, will remain as part of the project listings +but may be pushed down the list, and may not be considered as a +featured project while in this status. If it was a featured project +at the time of being flagged due to a strike, it will be removed from +the featured list.

+

Three Months Inactivity

+

If the project has been in hiatus for three months, without having +notified the network, it is considered dead and removed from the +project listings. E-mail webmaster@handmadedev.org +to re-instate it the first time this occurs.

+

The second time a project reaches this status, without having notified +the network, it will be considered permanently dead. At this point, +the project will need to be re-submitted.

+

How to be Featured

+

More active projects have a better chance at being noticed by members and +staff alike. Popular projects are more likely to receive votes +towards a monthly community featured-project slot, though community +interest can be piqued even by small or new projects if the project +is producing interesting content. Community-chosen featured-project +slots make up to 3 of the possible highlighted projects -- there are +also up to 3 staff-pick featured slots each month. The recipients of +these slots are at the discretion of the site staff, but you can make +your project shine by frequently and consistently reporting progress +on your project and interacting with the community.

diff --git a/oldstaticpages/press.txt b/oldstaticpages/press.txt new file mode 100644 index 00000000..48e2b290 --- /dev/null +++ b/oldstaticpages/press.txt @@ -0,0 +1,21 @@ + +

+To refer to the organization developing this website, please use "Handmade +Dev". To refer to this website directly, use "the Handmade Network" or +"handmade.network" (with that capitalisation). Handmade Dev is not yet an +official organization, but we are working on filing paperwork to register as a +501(c) Non-Profit. +

+ +

+If you wish to include an official logo for the website in an external piece of +work (news article, linkback, credit, etc), please use one of the following +logos, sized as appropriate. The variations with transparent backgrounds may be +recolored as appropriate for the surrounding context. If you would like higher +resolution images or vector outlines, please email one of the staff with your request. +

+ + + + + diff --git a/oldstaticpages/projectguidelines.txt b/oldstaticpages/projectguidelines.txt new file mode 100644 index 00000000..45c9e9aa --- /dev/null +++ b/oldstaticpages/projectguidelines.txt @@ -0,0 +1,199 @@ +

Project +Submission Guidelines

+


+

+

At Handmade Network, we +pride ourselves on being open to hosting all sorts of projects, from +low level tools to games to conscientious web applications to +innumerable other carefully crafted varieties of software. With this +in mind, there are certain standards of content quality that we +expect projects hosted on the site to meet, and certain +characteristics that we believe projects should uphold in order to +best contribute to the community of software development we are +attempting to cultivate. +

+


+

+

This document outlines +these standards and characteristics as specifically as possible, +allowing for exceptions both in favor and against project approval if +unanticipated circumstances arise. All project approvals or +rejections will be accompanied by a justification, and we expect to +be held accountable if the provided justification is insufficiently +backed by these guidelines.

+


+

+

Content Quality

+

A +high-quality Handmade Network project submission will:

+
    +
  1. Have a relevant + and informative blurb which + gives those browsing project listings a basic idea of the project's + purpose and value. The 'elevator pitch', if you will.

    +
  2. Have a thorough + description, which will give + visitors several vital pieces of information in understanding the + project:

    +
      +
    1. + The background / impetus for the project’s creation

      +
    2. + The project's short-term and long-term goals

      +
    3. + The current status of the project

      +
    4. The + road-map to reaching these goals, both short- and long-term

      +
    +
+

Excellent +descriptions will also make use of (BBCode) markup to make it easier +for readers to scan for relevant information.

+
    +
  1. Provide links + to other relevant websites where visitors may find out more + about project development or the current activity of the author(s). + Common examples:

    +
      +
    1. A YouTube channel + where the author(s) showcase latest features or record development + logs

      +
    2. An external + project homepage where new development builds can be found

      +
    3. An external + source-code hosting site where the project's source code is + available

      +
    4. A social media + page for the project or the author(s) showing development progress

      +
    +
  2. Provide several + screenshots showcasing the current state of project development, + if the project is visually oriented. Examples:

    +
      +
    1. A project with a + graphical user interface should show what a typical user will see + when interacting with the running program

      +
    2. Same goes for + command line tool with a curses-like TUI

      +
    3. A game or game + engine should provide several screenshots to give visitors an idea + of what makes it unique

      +
    4. Visually-oriented + projects with support for multiple platforms should provide at + least one screenshot of the project running on each platform

      +
    +
  3. Where possible, + provide current builds of the project that visitors can run to + see the state of the project.

    +
  4. Customize the + project page with appropriate colors or background images to + give the project a sense of visual identity.

    +
+


+

+

Providing as much +information about your project as possible will help us decide +whether it meets the qualifications listed below, and whether +community members will be invested in your project's success.

+


+

+

Acceptable Projects

+

For +us to be willing to approve a project on the site, it should:

+
    +
  1. Be a + legitimate, unique, and original work. + We unequivocally will not accept projects which are blatantly + advertisements for unrelated websites or companies, projects which + are indistinguishable from others on the site, projects which are + incorrectly attributed to someone other than the actual creator. +

    +
  2. Have a + meaningful amount of development work planned or completed above its + dependencies, parent project, or previous incarnation. + We will not accept projects which cannot prove themselves to be more + than a thin layer of “glue code” above several libraries, or + which are barely-modified forks of other projects.

    +
  3. Be the + development effort of an individual or small team, organization or + company. We wish to keep the + focus of this site on projects which highlight the inspiring work of + small developers, projects which provide a high ratio of value-added + to man-hours worked, and the exploration of software creation as a + craft. We will refrain from defining “small” for the purposes of + this guideline, and instead give examples of approvals and + rejections:

    +
      +
    1. + A commercial compression tool developed by a small team of 2-4 + programmers and a few miscellaneous staff under the umbrella of an + LLC would be approved.

      +
    2. + A suite of productivity software developed by a large multinational + corporation would be rejected, as it is not in line with the + intended focus of the site, and such a corporation is likely + already receiving widespread exposure elsewhere and may have their + own extensive user community.

      +
    3. + An existing, well-established open-source project with a + contributor count at the time of submission in the thousands may be + approved or rejected depending on other factors like the age of the + project, the number of current contributors, the content to be + provided on Handmade Network vs the project's homepage, and the + current state of development of the project.

      +
    +
  4. Enrich to the + community by releasing + finished projects for community members to use or purchase and + providing regular updates that inform and educate community members. + We heavily encourage activities such as:

    +
      +
    1. + Writing or recording logs/articles explaining decisions/trade-offs + made during development and their rationale, theory behind a piece + of code, information discovered about an API or dependency.

      +
    2. + Releasing demos, samples, or pre-release versions demonstrating the + mechanics or evolution of a particular feature

      +
    3. Responding + to questions about functionality in comments/forums.

      +
    +
+

For + more information on this, please see our Monthly Update Policy + requirements and suggestions.

+
    +
  1. Have a clear, + achievable goal or set of goals, + and provide lasting value to its intended audience. + We want to discourage projects from falling into development limbo + and slowly dying off. We also want to encourage project owners to + support their projects past release, so that they might have + continuing usefulness as the dependencies and platforms they rely on + continue to develop.

    +
+


+

+

Additionally, +there are some criteria that we guarantee will not +be considered in approving or rejecting a project:

+
    +
  1. Implementation + language. A language is merely + a tool, and worthwhile software can be created in any language or + environment.

    +
  2. Use (or not) of + libraries. While we are + supportive of efforts to develop software that makes minimal use of + libraries, and to write code that supplants existing libraries, this + is by no means a requirement of projects hosted on the site. We + merely ask, as stated above, that projects perform some significant + work above that which its dependencies perform on their own.

    +
  3. License, + monetization, and source-code provision. + We do not require projects be open-source, free software, etc. While + we are very supportive of these movements and the principles behind + them, we also understand the need to make a living off of + development work, and wish to make it possible for any project to + contribute to and benefit from our software development community.

    +
diff --git a/resetdb.sh b/resetdb.sh index 75316cf4..a79775a4 100755 --- a/resetdb.sh +++ b/resetdb.sh @@ -9,8 +9,8 @@ set -eou pipefail # TODO(opensource): We should adapt Asaf's seedfile command and then delete this. THIS_PATH=$(pwd) -#BETA_PATH='/mnt/c/Users/bvisn/Developer/handmade/handmade-beta' -BETA_PATH='/Users/benvisness/Developer/handmade/handmade-beta' +BETA_PATH='/mnt/c/Users/bvisn/Developer/handmade/handmade-beta' +# BETA_PATH='/Users/benvisness/Developer/handmade/handmade-beta' cd $BETA_PATH docker-compose down -v @@ -22,5 +22,5 @@ cd $THIS_PATH go run src/main.go migrate 2021-03-10T05:16:21Z cd $BETA_PATH -#./scripts/db_import -d -n hmn_two -a ./dbdumps/hmn_pg_dump_2020-11-10 -./scripts/db_import -d -n hmn_two -a ./dbdumps/hmn_pg_dump_2021-04-25 +./scripts/db_import -d -n hmn_two -a ./dbdumps/hmn_pg_dump_2021-04-26 +# ./scripts/db_import -d -n hmn_two -a ./dbdumps/hmn_pg_dump_2021-04-25 diff --git a/src/migration/migration.go b/src/migration/migration.go index 62621101..84a46ec1 100644 --- a/src/migration/migration.go +++ b/src/migration/migration.go @@ -317,18 +317,21 @@ func SeedFromFile(seedFile string, afterMigration types.MigrationVersion) { ) // NOTE(asaf): We have to use the low-level API of pgconn, because the pgx Exec always wraps the query in a transaction. lowLevelConn, err := pgconn.Connect(ctx, template1DSN) + if err != nil { + panic(fmt.Errorf("failed to connect to db: %w", err)) + } defer lowLevelConn.Close(ctx) - result := lowLevelConn.ExecParams(ctx, "DROP DATABASE hmn", nil, nil, nil, nil) + result := lowLevelConn.ExecParams(ctx, fmt.Sprintf("DROP DATABASE %s", config.Config.Postgres.DbName), nil, nil, nil, nil) _, err = result.Close() pgErr, isPgError := err.(*pgconn.PgError) if err != nil { - if !isPgError || pgErr.SQLState() != "3D000" { // NOTE(asaf): 3D000 means "Database does not exist" + if !(isPgError && pgErr.SQLState() == "3D000") { // NOTE(asaf): 3D000 means "Database does not exist" panic(fmt.Errorf("failed to drop db: %w", err)) } } - result = lowLevelConn.ExecParams(ctx, "CREATE DATABASE hmn", nil, nil, nil, nil) + result = lowLevelConn.ExecParams(ctx, fmt.Sprintf("CREATE DATABASE %s", config.Config.Postgres.DbName), nil, nil, nil, nil) _, err = result.Close() if err != nil { panic(fmt.Errorf("failed to create db: %w", err)) diff --git a/src/migration/migrationTemplate.txt b/src/migration/migrationTemplate.txt index d8490be4..53b4e355 100644 --- a/src/migration/migrationTemplate.txt +++ b/src/migration/migrationTemplate.txt @@ -1,6 +1,7 @@ package migrations import ( + "context" "time" "git.handmade.network/hmn/hmn/src/migration/types" diff --git a/src/migration/migrations/2021-04-26T000000Z_DeleteStaticPages.go b/src/migration/migrations/2021-04-26T000000Z_DeleteStaticPages.go new file mode 100644 index 00000000..d0bbf588 --- /dev/null +++ b/src/migration/migrations/2021-04-26T000000Z_DeleteStaticPages.go @@ -0,0 +1,90 @@ +package migrations + +import ( + "context" + "fmt" + "time" + + "git.handmade.network/hmn/hmn/src/migration/types" + "git.handmade.network/hmn/hmn/src/oops" + "github.com/jackc/pgx/v4" +) + +func init() { + registerMigration(DeleteStaticPages{}) +} + +type DeleteStaticPages struct{} + +func (m DeleteStaticPages) Version() types.MigrationVersion { + return types.MigrationVersion(time.Date(2021, 4, 26, 0, 0, 0, 0, time.UTC)) +} + +func (m DeleteStaticPages) Name() string { + return "DeleteStaticPages" +} + +func (m DeleteStaticPages) Description() string { + return "Delete static page posts" +} + +func (m DeleteStaticPages) Up(ctx context.Context, tx pgx.Tx) error { + res, err := tx.Exec(ctx, + ` + ALTER TABLE handmade_project + DROP static_id; + `, + ) + if err != nil { + return oops.New(err, "failed to drop static_id column") + } + + res, err = tx.Exec(ctx, + ` + DELETE FROM handmade_post + WHERE id IN ( + SELECT post.id + FROM + handmade_post AS post + LEFT JOIN handmade_thread AS thread ON post.thread_id = thread.id + LEFT JOIN handmade_category AS threadcat ON thread.category_id = threadcat.id + LEFT JOIN handmade_category AS postcat ON post.category_id = postcat.id + WHERE + threadcat.kind = 3 + OR postcat.kind = 3 + ); + `, + ) + if err != nil { + return oops.New(err, "failed to delete the posts") + } + fmt.Printf("Deleted %v static pages.\n", res.RowsAffected()) + + _, err = tx.Exec(ctx, + ` + DELETE FROM handmade_thread + WHERE id IN ( + SELECT thread.id + FROM + handmade_thread AS thread + JOIN handmade_category AS cat ON thread.category_id = cat.id + WHERE + cat.kind = 3 + ); + `, + ) + if err != nil { + return oops.New(err, "failed to delete the threads") + } + + _, err = tx.Exec(ctx, `DELETE FROM handmade_category WHERE kind = 3`) + if err != nil { + return oops.New(err, "failed to delete the categories") + } + + return nil +} + +func (m DeleteStaticPages) Down(ctx context.Context, tx pgx.Tx) error { + panic("Implement me") +} diff --git a/src/migration/migrations/2021-04-26T000001Z_FixPostCategoryId.go b/src/migration/migrations/2021-04-26T000001Z_FixPostCategoryId.go new file mode 100644 index 00000000..7f953ade --- /dev/null +++ b/src/migration/migrations/2021-04-26T000001Z_FixPostCategoryId.go @@ -0,0 +1,53 @@ +package migrations + +import ( + "context" + "time" + + "git.handmade.network/hmn/hmn/src/migration/types" + "git.handmade.network/hmn/hmn/src/oops" + "github.com/jackc/pgx/v4" +) + +func init() { + registerMigration(FixPostCategoryId{}) +} + +type FixPostCategoryId struct{} + +func (m FixPostCategoryId) Version() types.MigrationVersion { + return types.MigrationVersion(time.Date(2021, 4, 26, 0, 0, 0, 1, time.UTC)) +} + +func (m FixPostCategoryId) Name() string { + return "FixPostCategoryId" +} + +func (m FixPostCategoryId) Description() string { + return "Copy category id from thread" +} + +func (m FixPostCategoryId) Up(ctx context.Context, tx pgx.Tx) error { + _, err := tx.Exec(ctx, + ` + UPDATE handmade_post + SET (category_id) = ( + SELECT thread.category_id + FROM + handmade_thread AS thread + JOIN handmade_post AS post ON post.thread_id = thread.id + WHERE + post.id = handmade_post.id + ) + `, + ) + if err != nil { + return oops.New(err, "failed to migrate data from categories") + } + + return nil +} + +func (m FixPostCategoryId) Down(ctx context.Context, tx pgx.Tx) error { + panic("Implement me") +} diff --git a/src/migration/migrations/2021-04-26T235720Z_AddCommonFieldsToPosts.go b/src/migration/migrations/2021-04-26T235720Z_AddCommonFieldsToPosts.go new file mode 100644 index 00000000..9659d4c4 --- /dev/null +++ b/src/migration/migrations/2021-04-26T235720Z_AddCommonFieldsToPosts.go @@ -0,0 +1,124 @@ +package migrations + +import ( + "context" + "time" + + "git.handmade.network/hmn/hmn/src/migration/types" + "git.handmade.network/hmn/hmn/src/oops" + "github.com/jackc/pgx/v4" +) + +func init() { + registerMigration(AddCommonFieldsToPosts{}) +} + +type AddCommonFieldsToPosts struct{} + +func (m AddCommonFieldsToPosts) Version() types.MigrationVersion { + return types.MigrationVersion(time.Date(2021, 4, 26, 23, 57, 20, 0, time.UTC)) +} + +func (m AddCommonFieldsToPosts) Name() string { + return "AddCommonFieldsToPosts" +} + +func (m AddCommonFieldsToPosts) Description() string { + return "Adds project and category info directly to posts for more efficient queries" +} + +func (m AddCommonFieldsToPosts) Up(ctx context.Context, tx pgx.Tx) error { + _, err := tx.Exec(ctx, + ` + ALTER TABLE handmade_post + ADD category_kind INT, + ADD project_id INT REFERENCES handmade_project (id) ON DELETE RESTRICT; + `, + ) + if err != nil { + return oops.New(err, "failed to add columns") + } + + _, err = tx.Exec(ctx, + ` + UPDATE handmade_post + SET (category_id, category_kind, project_id) = ( + SELECT cat.id, cat.kind, cat.project_id + FROM + handmade_category AS cat + JOIN handmade_thread AS thread ON thread.category_id = cat.id + JOIN handmade_post AS post ON post.thread_id = thread.id + WHERE + post.id = handmade_post.id + ) + `, + ) + if err != nil { + return oops.New(err, "failed to migrate data from categories") + } + + _, err = tx.Exec(ctx, + ` + CREATE FUNCTION category_id_for_thread(int) returns int as $$ + SELECT thread.category_id + FROM handmade_thread AS thread + WHERE thread.id = $1 + $$ LANGUAGE SQL; + + CREATE FUNCTION category_kind_for_post(int) returns int as $$ + SELECT cat.kind + FROM + handmade_post AS post + JOIN handmade_thread AS thread ON post.thread_id = thread.id + JOIN handmade_category AS cat ON thread.category_id = cat.id + WHERE post.id = $1 + $$ LANGUAGE SQL; + + CREATE FUNCTION project_id_for_post(int) returns int as $$ + SELECT cat.project_id + FROM + handmade_post AS post + JOIN handmade_thread AS thread ON post.thread_id = thread.id + JOIN handmade_category AS cat ON thread.category_id = cat.id + WHERE post.id = $1 + $$ LANGUAGE SQL; + + ALTER TABLE handmade_post + ALTER category_kind SET NOT NULL, + ALTER thread_id SET NOT NULL, + ALTER project_id SET NOT NULL, + ADD CONSTRAINT post_category_id_from_thread CHECK ( + category_id_for_thread(thread_id) = category_id + ), + ADD CONSTRAINT post_category_kind_from_category CHECK ( + category_kind_for_post(id) = category_kind + ), + ADD CONSTRAINT post_project_id_from_category CHECK ( + project_id_for_post(id) = project_id + ); + `, + ) + if err != nil { + return oops.New(err, "failed to add constraints") + } + + _, err = tx.Exec(ctx, + ` + CREATE INDEX post_project_id ON handmade_post (project_id); + CREATE INDEX post_category_kind ON handmade_post (category_kind); + CREATE INDEX post_postdate ON handmade_post (postdate DESC); + + CREATE INDEX clri_user_id ON handmade_categorylastreadinfo (user_id); + CREATE INDEX tlri_user_id ON handmade_threadlastreadinfo (user_id); + `, + ) + if err != nil { + return oops.New(err, "failed to create indexes") + } + + return nil +} + +func (m AddCommonFieldsToPosts) Down(ctx context.Context, tx pgx.Tx) error { + panic("Implement me") +} diff --git a/src/models/post.go b/src/models/post.go index 8dc4d907..76b429de 100644 --- a/src/models/post.go +++ b/src/models/post.go @@ -12,8 +12,11 @@ type Post struct { AuthorID *int `db:"author_id"` CategoryID int `db:"category_id"` ParentID *int `db:"parent_id"` - ThreadID *int `db:"thread_id"` // TODO: This is only null for posts that are actually static pages. Which probably shouldn't be posts anyway. Plz make not null thanks + ThreadID int `db:"thread_id"` CurrentID int `db:"current_id"` + ProjectID int `db:"project_id"` + + CategoryType CategoryType `db:"category_kind"` Depth int `db:"depth"` Slug string `db:"slug"` diff --git a/src/templates/src/index.html b/src/templates/src/index.html index 7d2d5e06..2b768760 100644 --- a/src/templates/src/index.html +++ b/src/templates/src/index.html @@ -269,11 +269,9 @@
-
- -
- {{ .User.Name }}{{ relativedate .Date }} -
+ +
+ {{ .User.Name }}{{ relativedate .Date }}
diff --git a/src/templates/urls.go b/src/templates/urls.go index 2e252bf4..38a73114 100644 --- a/src/templates/urls.go +++ b/src/templates/urls.go @@ -11,9 +11,9 @@ func PostUrl(post models.Post, catType models.CategoryType, subdomain string) st switch catType { // TODO: All the relevant post types. Maybe it doesn't make sense to lump them all together here. case models.CatTypeBlog: - return hmnurl.ProjectUrl(fmt.Sprintf("blogs/p/%d/e/%d", *post.ThreadID, post.ID), nil, subdomain) + return hmnurl.ProjectUrl(fmt.Sprintf("blogs/p/%d/e/%d", post.ThreadID, post.ID), nil, subdomain) case models.CatTypeForum: - return hmnurl.ProjectUrl(fmt.Sprintf("forums/t/%d/p/%d", *post.ThreadID, post.ID), nil, subdomain) + return hmnurl.ProjectUrl(fmt.Sprintf("forums/t/%d/p/%d", post.ThreadID, post.ID), nil, subdomain) } return "" diff --git a/src/website/feed.go b/src/website/feed.go index 58803674..cb1e1803 100644 --- a/src/website/feed.go +++ b/src/website/feed.go @@ -29,9 +29,8 @@ func Feed(c *RequestContext) ResponseData { SELECT COUNT(*) FROM handmade_post AS post - JOIN handmade_category AS cat ON cat.id = post.category_id WHERE - cat.kind IN ($1, $2, $3, $4) + post.category_kind IN ($1, $2, $3, $4) AND NOT moderated `, models.CatTypeForum, @@ -75,13 +74,12 @@ func Feed(c *RequestContext) ResponseData { c.Perf.StartBlock("SQL", "Fetch posts") type feedPostQuery struct { - Post models.Post `db:"post"` - Thread models.Thread `db:"thread"` - Cat models.Category `db:"cat"` - Proj models.Project `db:"proj"` - User models.User `db:"auth_user"` - ThreadLastReadTime *time.Time `db:"tlri.lastread"` - CatLastReadTime *time.Time `db:"clri.lastread"` + Post models.Post `db:"post"` + Thread models.Thread `db:"thread"` + Proj models.Project `db:"proj"` + User models.User `db:"auth_user"` + ThreadLastReadTime *time.Time `db:"tlri.lastread"` + CatLastReadTime *time.Time `db:"clri.lastread"` } posts, err := db.Query(c.Context(), c.Conn, feedPostQuery{}, ` @@ -89,19 +87,18 @@ func Feed(c *RequestContext) ResponseData { FROM handmade_post AS post JOIN handmade_thread AS thread ON thread.id = post.thread_id - JOIN handmade_category AS cat ON cat.id = thread.category_id - JOIN handmade_project AS proj ON proj.id = cat.project_id + JOIN handmade_project AS proj ON proj.id = post.project_id LEFT OUTER JOIN handmade_threadlastreadinfo AS tlri ON ( - tlri.thread_id = thread.id + tlri.thread_id = post.thread_id AND tlri.user_id = $1 ) LEFT OUTER JOIN handmade_categorylastreadinfo AS clri ON ( - clri.category_id = cat.id + clri.category_id = post.category_id AND clri.user_id = $1 ) LEFT OUTER JOIN auth_user ON post.author_id = auth_user.id WHERE - cat.kind IN ($2, $3, $4, $5) + post.category_kind IN ($2, $3, $4, $5) AND post.moderated = FALSE AND post.thread_id IS NOT NULL ORDER BY postdate DESC @@ -131,7 +128,8 @@ func Feed(c *RequestContext) ResponseData { hasRead = true } - parents := postResult.Cat.GetHierarchy(c.Context(), c.Conn) + var parents []models.Category + // parents := postResult.Cat.GetHierarchy(c.Context(), c.Conn) logging.Debug().Interface("parents", parents).Msg("") var breadcrumbs []templates.Breadcrumb @@ -148,7 +146,7 @@ func Feed(c *RequestContext) ResponseData { postItems = append(postItems, templates.PostListItem{ Title: postResult.Thread.Title, - Url: templates.PostUrl(postResult.Post, postResult.Cat.Kind, postResult.Proj.Subdomain()), + Url: templates.PostUrl(postResult.Post, postResult.Post.CategoryType, postResult.Proj.Subdomain()), User: templates.UserToTemplate(&postResult.User), Date: postResult.Post.PostDate, Breadcrumbs: breadcrumbs, diff --git a/src/website/landing.go b/src/website/landing.go index 034df5ce..77461f25 100644 --- a/src/website/landing.go +++ b/src/website/landing.go @@ -75,34 +75,31 @@ func Index(c *RequestContext) ResponseData { c.Perf.StartBlock("SQL", fmt.Sprintf("Fetch posts for %s", *proj.Name)) type projectPostQuery struct { - Post models.Post `db:"post"` - Thread models.Thread `db:"thread"` - Cat models.Category `db:"cat"` - User models.User `db:"auth_user"` - ThreadLastReadTime *time.Time `db:"tlri.lastread"` - CatLastReadTime *time.Time `db:"clri.lastread"` + Post models.Post `db:"post"` + Thread models.Thread `db:"thread"` + User models.User `db:"auth_user"` + ThreadLastReadTime *time.Time `db:"tlri.lastread"` + CatLastReadTime *time.Time `db:"clri.lastread"` } projectPostIter, err := db.Query(c.Context(), c.Conn, projectPostQuery{}, ` SELECT $columns FROM handmade_post AS post - JOIN handmade_thread AS thread ON thread.id = post.thread_id - JOIN handmade_category AS cat ON cat.id = thread.category_id - LEFT OUTER JOIN handmade_threadlastreadinfo AS tlri ON ( - tlri.thread_id = thread.id + JOIN handmade_thread AS thread ON post.thread_id = thread.id + LEFT JOIN handmade_threadlastreadinfo AS tlri ON ( + tlri.thread_id = post.thread_id AND tlri.user_id = $1 ) - LEFT OUTER JOIN handmade_categorylastreadinfo AS clri ON ( - clri.category_id = cat.id + LEFT JOIN handmade_categorylastreadinfo AS clri ON ( + clri.category_id = post.category_id AND clri.user_id = $1 ) - LEFT OUTER JOIN auth_user ON post.author_id = auth_user.id + LEFT JOIN auth_user ON post.author_id = auth_user.id WHERE - cat.project_id = $2 - AND cat.kind IN ($3, $4, $5, $6) + post.project_id = $2 + AND post.category_kind IN ($3, $4, $5, $6) AND post.moderated = FALSE - AND post.thread_id IS NOT NULL ORDER BY postdate DESC LIMIT $7 `, @@ -133,7 +130,7 @@ func Index(c *RequestContext) ResponseData { } featurable := (!proj.IsHMN() && - projectPost.Cat.Kind == models.CatTypeBlog && + projectPost.Post.CategoryType == models.CatTypeBlog && projectPost.Post.ParentID == nil && landingPageProject.FeaturedPost == nil) @@ -159,7 +156,7 @@ func Index(c *RequestContext) ResponseData { landingPageProject.FeaturedPost = &LandingPageFeaturedPost{ Title: projectPost.Thread.Title, - Url: templates.PostUrl(projectPost.Post, projectPost.Cat.Kind, proj.Subdomain()), + Url: templates.PostUrl(projectPost.Post, projectPost.Post.CategoryType, proj.Subdomain()), User: templates.UserToTemplate(&projectPost.User), Date: projectPost.Post.PostDate, Unread: !hasRead, @@ -168,7 +165,7 @@ func Index(c *RequestContext) ResponseData { } else { landingPageProject.Posts = append(landingPageProject.Posts, templates.PostListItem{ Title: projectPost.Thread.Title, - Url: templates.PostUrl(projectPost.Post, projectPost.Cat.Kind, proj.Subdomain()), + Url: templates.PostUrl(projectPost.Post, projectPost.Post.CategoryType, proj.Subdomain()), User: templates.UserToTemplate(&projectPost.User), Date: projectPost.Post.PostDate, Unread: !hasRead,