feat: update

This commit is contained in:
Damillora 2021-05-10 22:47:35 +07:00
parent c978c6eb50
commit dbff5649d7
18 changed files with 8000 additions and 232 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
Dockerfile

28
.github/workflows/workflow.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: CI
on: push
jobs:
release:
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
push: true
tags: damillora/shioriko:latest
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

View File

@ -4,13 +4,21 @@ WORKDIR /go/src/shioriko
COPY . .
RUN go get -d -v ./...
RUN CGO_ENABLED=0 GOOS=linux go build -o /shioriko
RUN go build -o /shioriko
RUN mkdir -p /web && cp -r web/static web/template /web
FROM scratch AS runtime
FROM node:14-alpine AS node_build
WORKDIR /src
COPY . .
WORKDIR /src/web/app
RUN yarn install && yarn build
WORKDIR /
COPY --from=build /shioriko /
COPY --from=build /web /web
FROM alpine AS runtime
ENTRYPOINT ["/shioriko"]
RUN mkdir -p /app/web
WORKDIR /app
COPY --from=build /shioriko /app
COPY --from=node_build /src/web/static /app/web/static
COPY --from=node_build /src/web/template /app/web/template
ENTRYPOINT ["/app/shioriko"]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Damillora
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
README.md Normal file
View File

@ -0,0 +1,30 @@
# Shioriko
A booru-like software written in Go and Svelte.
## Installation
The easiest way to get started is to use Docker:
```bash
docker pull damillora/shioriko
```
## Requirements
* PostgreSQL database
## Configuration
Shioriko is configured using environment variables:
* `POSTGRES_DATABASE`: DSN string of Postgres Database, see [Gorm documentation](https://gorm.io/docs/connecting_to_the_database.html)
* `AUTH_SECRET`: Secret used to sign JWTs
* `DATA_DIR`: Data directory to store images
* `BASE_URL`: Accesible URL of the instance
* `DISABLE_REGISTRATION`: Optional, disable registration on the instance
## Contributing
Shioriko is still in an early stage, but contributions are welcome!
## License
[MIT](https://choosealicense.com/licenses/mit/)

View File

@ -72,7 +72,7 @@ func postGetTag(c *gin.Context) {
Tags: tagStrings,
})
}
postPages := services.CountPostPages()
postPages := services.CountPostPagesTag(tag)
c.JSON(http.StatusOK, models.PostPaginationResponse{
CurrentPage: page,
TotalPage: postPages,

View File

@ -78,6 +78,16 @@ func CountPostPages() int {
database.DB.Model(&database.Post{}).Count(&count)
return int(count/perPage) + 1
}
func CountPostPagesTag(tagSyntax string) int {
tag, err := GetTag(tagSyntax)
if err != nil {
return 0
}
var count int64
count = database.DB.Model(&tag).Joins("Blob").Preload("Tags").Preload("Tags.TagType").Association("Posts").Count()
return int(count/perPage) + 1
}
func DeletePost(id string) error {

View File

@ -1,5 +1,8 @@
<script>
import { Router, Link, Route } from "svelte-routing";
import Navbar from "./Navbar.svelte";
import Home from "./routes/Home.svelte";
import Posts from "./routes/Posts.svelte";
import Post from "./routes/Post.svelte";
@ -7,69 +10,21 @@
import Logout from "./routes/Logout.svelte";
import Tag from "./routes/Tag.svelte";
import { token } from "./stores.js"
let loggedIn = false;
token.subscribe(value => {
loggedIn = value !== ""
});
export let url = "";
let baseURL = window.BASE_URL;
</script>
<Router url="{url}">
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<Link class="navbar-item" to="/">
Shioriko
</Link>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<Link class="navbar-item" to="/posts">
Posts
</Link>
</div>
<div class="navbar-end">
{#if loggedIn}
<div class="navbar-item">
<div class="buttons">
<Link to="/auth/logout" class="button is-light">
Log out
</Link>
</div>
</div>
{:else}
<div class="navbar-item">
<div class="buttons">
<Link to="/auth/register" class="button is-primary">
<strong>Register</strong>
</Link>
<Link to="/auth/login" class="button is-light">
Log in
</Link>
</div>
</div>
{/if}
</div>
</div>
</nav>
<div>
<Route path="/" component="{Home}" />
<Route path="/posts" component="{Posts}" />
<Route path="/tag/:id" component="{Tag}" />
<Route path="/post/:id" component="{Post}" />
<Route path="/auth/login" component="{Login}" />
<Route path="/auth/logout" component="{Logout}" />
</div>
<Router {url}>
<Navbar />
<div>
<Route path="/" component={Home} />
<Route path="/posts" component={Posts} />
<Route path="/tag/:id" component={Tag} />
<Route path="/post/:id" component={Post} />
<Route path="/auth/login" component={Login} />
<Route path="/auth/logout" component={Logout} />
</div>
</Router>

View File

@ -0,0 +1,16 @@
<script>
import { token } from "./stores.js";
import { navigate } from "svelte-routing";
import { onMount } from "svelte";
let loggedIn = false;
token.subscribe((value) => {
loggedIn = value !== "";
});
onMount(() => {
if (loggedIn === false) {
navigate("/auth/login");
}
});
</script>

66
web/app/src/Navbar.svelte Normal file
View File

@ -0,0 +1,66 @@
<script>
import { Link } from "svelte-routing";
import { token } from "./stores.js";
let menu_shown = false;
let loggedIn = false;
token.subscribe((value) => {
loggedIn = value !== "";
});
const toggleMenu = () => {
menu_shown = !menu_shown;
};
</script>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<Link class="navbar-item" to="/">Shioriko</Link>
<a
href={"#"}
on:click={toggleMenu}
role="button"
class="navbar-burger"
aria-label="menu"
aria-expanded="false"
>
<span aria-hidden="true" />
<span aria-hidden="true" />
<span aria-hidden="true" />
</a>
</div>
<div class="navbar-menu" class:is-active={menu_shown}>
<div class="navbar-start">
<Link class="navbar-item" to="/posts">Posts</Link>
{#if loggedIn}
<Link class="navbar-item" to="/upload">Upload</Link>
{/if}
</div>
<div class="navbar-end">
{#if loggedIn}
<div class="navbar-item">
<div class="buttons">
<Link to="/auth/logout" class="button is-light">
Log out
</Link>
</div>
</div>
{:else}
<div class="navbar-item">
<div class="buttons">
<Link to="/auth/register" class="button is-primary">
<strong>Register</strong>
</Link>
<Link to="/auth/login" class="button is-light">
Log in
</Link>
</div>
</div>
{/if}
</div>
</div>
</nav>

View File

@ -1,14 +1,9 @@
<script>
</script>
<section class="hero is-primary is-medium">
<div class="hero-body">
<p class="title">
Shioriko
</p>
<p class="subtitle">
Booru-style gallery written in Go and Svelte
</p>
</div>
</section>
<div class="hero-body">
<p class="title">Shioriko</p>
<p class="subtitle">Booru-style gallery written in Go and Svelte</p>
</div>
</section>

View File

@ -1,33 +1,48 @@
<script>
import { login } from "../api.js"
import { navigate } from "svelte-routing"
import { login } from "../api.js";
import { navigate } from "svelte-routing";
let username = "";
let password = "";
const doLogin = async () => {
const tokenData = await login({ username, password});
const tokenData = await login({ username, password });
navigate("/");
}
};
</script>
<div class="container">
<form on:submit|preventDefault={doLogin}>
<div class="field">
<label class="label">Username</label>
<div class="control">
<input class="input" type="text" placeholder="Username" bind:value={username} required>
</div>
<label for="username" class="label">Username</label>
<div class="control">
<input
id="username"
class="input"
type="text"
placeholder="Username"
bind:value={username}
required
/>
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control">
<input class="input" type="password" placeholder="Password" bind:value={password} required>
</div>
<label for="password" class="label">Password</label>
<div class="control">
<input
id="password"
class="input"
type="password"
placeholder="Password"
bind:value={password}
required
/>
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-link">Login</button>
</div>
<div class="control">
<button class="button is-link">Login</button>
</div>
</div>
</form>
</div>

View File

@ -1,11 +1,10 @@
<script>
import { token } from "../stores.js"
import { navigate } from "svelte-routing"
import {onMount } from "svelte"
import { token } from "../stores.js";
import { navigate } from "svelte-routing";
import { onMount } from "svelte";
onMount(() => {
token.set("");
navigate("/");
});
</script>

View File

@ -50,7 +50,7 @@
</div>
<div class="column">
<figure class="image">
<img src="{post.image_path}">
<img alt="{post.id}" src="{post.image_path}">
</figure>
</div>
</div>

View File

@ -1,7 +1,7 @@
<script>
import { onMount } from "svelte";
import { getPosts } from "../api.js";
import { Link} from "svelte-routing";
import { Link } from "svelte-routing";
import queryString from "query-string";
export let location;
@ -10,34 +10,32 @@
let totalPages = 1;
let posts = [];
const getData = async () => {
const data = await getPosts({page});
if(Array.isArray(data.posts)) {
const data = await getPosts({ page });
if (Array.isArray(data.posts)) {
posts = data.posts;
totalPages = data.totalPage;
}
}
};
onMount(() => {
let queryParams;
$: queryParams = queryString.parse(location.search);
if(queryParams.page) {
page = parseInt(queryParams.page);
}
let queryParams;
queryParams = queryString.parse(location.search);
if (queryParams.page) {
page = parseInt(queryParams.page);
}
getData();
})
});
const handlePage = (i) => {
return () => {
page = i;
getData();
}
}
};
};
</script>
<section class="hero is-primary">
<div class="hero-body">
<p class="title">
Posts
</p>
<p class="title">Posts</p>
</div>
</section>
@ -45,63 +43,94 @@
<div class="container">
<nav class="pagination" role="navigation" aria-label="pagination">
{#if page > 1}
<a class="pagination-previous">Previous</a>
<Link
on:click={handlePage(page - 1)}
to="/posts?page={page - 1}"
class="pagination-previous"
aria-label="Previous">Previous</Link
>
{/if}
{#if page < totalPages}
<a class="pagination-next">Next page</a>
<Link
on:click={handlePage(page + 1)}
to="/posts?page={page + 1}"
class="pagination-next"
aria-label="Next">Next</Link
>
{/if}
<ul class="pagination-list">
{#if page > 3}
<li>
<Link on:click="{handlePage(1)}" to="/posts?page={1}" class="pagination-link" aria-label="Goto page 1">1</Link>
</li>
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
{/if}
{#each [...Array(5).keys()].map(x => x + page - 2) as i }
{#if i >= 1 && i <= totalPages}
{#if i == page}
<li>
<Link on:click="{handlePage(i)}" to="/posts?page={i}" class="pagination-link is-current" aria-label="Goto page {i}">{i}</Link>
</li>
{:else}
<li>
<Link on:click="{handlePage(i)}" to="/posts?page={i}" class="pagination-link" aria-label="Goto page {i}">{i}</Link>
</li>
{#if page > 3}
<li>
<Link
on:click={handlePage(1)}
to="/posts?page={1}"
class="pagination-link"
aria-label="Goto page 1">1</Link
>
</li>
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
{/if}
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
{#if i >= 1 && i <= totalPages}
{#if i == page}
<li>
<Link
on:click={handlePage(i)}
to="/posts?page={i}"
class="pagination-link is-current"
aria-label="Goto page {i}">{i}</Link
>
</li>
{:else}
<li>
<Link
on:click={handlePage(i)}
to="/posts?page={i}"
class="pagination-link"
aria-label="Goto page {i}">{i}</Link
>
</li>
{/if}
{/if}
{/each}
{#if totalPages - page > 2}
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
<li>
<Link
on:click={handlePage(totalPages)}
to="/posts?page={totalPages}"
class="pagination-link"
aria-label="Goto page {totalPages}"
>{totalPages}</Link
>
</li>
{/if}
{/if}
{/each}
{#if (totalPages - page) > 3}
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
<li>
<Link on:click="{handlePage(totalPages)}" to="/posts?page={totalPages}" class="pagination-link" aria-label="Goto page {totalPages}">{totalPages}</Link>
</li>
{/if}
</ul>
</nav>
</nav>
<div class="columns is-multiline">
{#each posts as post (post.id)}
<div class="column is-one-quarter card">
<div class="card-image">
<figure class="image">
<Link to="/post/{post.id}">
<img src="{post.image_path}">
</Link>
</figure>
</div>
<div class="card-content">
<div class="content">
{#each post.tags as tag (tag)}
<p>
<Link to="/tag/{tag}">{tag}</Link>
</p>
{/each}
<div class="column is-one-quarter card">
<div class="card-image">
<figure class="image">
<Link to="/post/{post.id}">
<img alt={post.id} src={post.image_path} />
</Link>
</figure>
</div>
<div class="card-content">
<div class="content">
{#each post.tags as tag (tag)}
<p>
<Link to="/tag/{tag}">{tag}</Link>
</p>
{/each}
</div>
</div>
</div>
</div>
{/each}
</div>
</div>

View File

@ -1,7 +1,7 @@
<script>
import { onMount } from "svelte";
import { getPostsTag } from "../api.js";
import { Link} from "svelte-routing";
import { Link } from "svelte-routing";
import queryString from "query-string";
export let location;
@ -12,37 +12,35 @@
let totalPages = 1;
let posts = [];
const getData = async () => {
const data = await getPostsTag({page, tag: id});
if(Array.isArray(data.posts)) {
const data = await getPostsTag({ page, tag: id });
if (Array.isArray(data.posts)) {
posts = data.posts;
totalPages = data.totalPage;
totalPages = data.totalPage;
}
}
};
onMount(() => {
let queryParams;
queryParams = queryString.parse(location.search);
if(queryParams.page) {
page = parseInt(queryParams.page);
}
let queryParams;
queryParams = queryString.parse(location.search);
if (queryParams.page) {
page = parseInt(queryParams.page);
}
getData();
})
});
const handlePage = (i) => {
return () => {
page = i;
getData();
}
}
};
};
</script>
<section class="hero is-primary">
<div class="hero-body">
<p class="title">
{id}
</p>
<p class="subtitle">
Tag
</p>
<p class="title">
{id}
</p>
<p class="subtitle">Tag</p>
</div>
</section>
@ -50,63 +48,94 @@
<div class="container">
<nav class="pagination" role="navigation" aria-label="pagination">
{#if page > 1}
<a class="pagination-previous">Previous</a>
<Link
on:click={handlePage(page - 1)}
to="/tag/{id}?page={page - 1}"
class="pagination-previous"
aria-label="Previous">Previous</Link
>
{/if}
{#if page < totalPages}
<a class="pagination-next">Next page</a>
<Link
on:click={handlePage(page + 1)}
to="/tag/{id}?page={page + 1}"
class="pagination-next"
aria-label="Next">Next</Link
>
{/if}
<ul class="pagination-list">
{#if page > 3}
<li>
<Link on:click="{handlePage(1)}" to="/tag/{id}?page={1}" class="pagination-link" aria-label="Goto page 1">1</Link>
</li>
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
{/if}
{#each [...Array(5).keys()].map(x => x + page - 2) as i }
{#if i >= 1 && i <= totalPages}
{#if i == page}
<li>
<Link on:click="{handlePage(i)}" to="/tag/{id}?page={i}" class="pagination-link is-current" aria-label="Goto page {i}">{i}</Link>
</li>
{:else}
<li>
<Link on:click="{handlePage(i)}" to="/tag/{id}?page={i}" class="pagination-link" aria-label="Goto page {i}">{i}</Link>
</li>
{#if page > 3}
<li>
<Link
on:click={handlePage(1)}
to="/tag/{id}?page={1}"
class="pagination-link"
aria-label="Goto page 1">1</Link
>
</li>
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
{/if}
{#each [...Array(5).keys()].map((x) => x + page - 2) as i}
{#if i >= 1 && i <= totalPages}
{#if i == page}
<li>
<Link
on:click={handlePage(i)}
to="/tag/{id}?page={i}"
class="pagination-link is-current"
aria-label="Goto page {i}">{i}</Link
>
</li>
{:else}
<li>
<Link
on:click={handlePage(i)}
to="/tag/{id}?page={i}"
class="pagination-link"
aria-label="Goto page {i}">{i}</Link
>
</li>
{/if}
{/if}
{/each}
{#if totalPages - page > 2}
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
<li>
<Link
on:click={handlePage(totalPages)}
to="/tag/{id}?page={totalPages}"
class="pagination-link"
aria-label="Goto page {totalPages}"
>{totalPages}</Link
>
</li>
{/if}
{/if}
{/each}
{#if (totalPages - page) > 3}
<li>
<span class="pagination-ellipsis">&hellip;</span>
</li>
<li>
<Link on:click="{handlePage(totalPages)}" to="/tag/{id}?page={totalPages}" class="pagination-link" aria-label="Goto page {totalPages}">{totalPages}</Link>
</li>
{/if}
</ul>
</nav>
</nav>
<div class="columns is-multiline">
{#each posts as post (post.id)}
<div class="column is-one-quarter card">
<div class="card-image">
<figure class="image">
<Link to="/post/{post.id}">
<img src="{post.image_path}">
</Link>
</figure>
</div>
<div class="card-content">
<div class="content">
{#each post.tags as tag (tag)}
<p>
<Link to="/tag/{tag}">{tag}</Link>
</p>
{/each}
<div class="column is-one-quarter card">
<div class="card-image">
<figure class="image">
<Link to="/post/{post.id}">
<img alt={post.id} src={post.image_path} />
</Link>
</figure>
</div>
<div class="card-content">
<div class="content">
{#each post.tags as tag (tag)}
<p>
<Link to="/tag/{tag}">{tag}</Link>
</p>
{/each}
</div>
</div>
</div>
</div>
{/each}
</div>
</div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long