DISTINCT funciona em todas as colunas no SELECT, então se você estiver SELECTing tudo, ele retornará cada linha distinta e não apenas as postagens distintas. Para contornar isso, você pode simplesmente SELECIONAR os dados da tabela de postagens e, em seguida, DISTINTAR, ou seja,
SELECT DISTINCT posts.*
Mas você também disse que gostaria das postagens e informações sobre gatos, se possível. Uma maneira de fazer isso e manter uma linha por post é usar GROUP_CONCAT então sua consulta pode terminar algo assim.
SELECT
posts.*,
GROUP_CONCAT(cats.id SEPARATOR ',') as catsList,
GROUP_CONCAT(tags.id SEPARATOR ',') as tagsList
FROM posts
INNER JOIN termRelations ON ( posts.id = termRelations.postId )
LEFT JOIN cats ON ( termRelations.termId = cats.id AND termRelations.termTypeId = 1 AND cats.id =5 )
LEFT JOIN tags ON ( termRelations.termId = tags.id AND termRelations.termTypeId = 0 AND
(tags.id =2
OR tags.id =1)
)
GROUP BY posts.id
LIMIT 0 , 30
Fiz algumas outras alterações em sua consulta original, como alterar a primeira junção para um INNER JOIN e adicionar os filtros cats/tags às condições JOIN para as tabelas relevantes.
ps quando você diz que tem tabelas separadas para gatos e tags para acelerar a geração de listas, você pode descobrir que uma tabela indexada corretamente seria tão rápida e também simplificaria seu código.