Compare commits
4 Commits
3e4380d9f8
...
ff0ab4c2c9
| Author | SHA1 | Date | |
|---|---|---|---|
|
ff0ab4c2c9
|
|||
|
77975c8f9d
|
|||
|
6daaaaa6fd
|
|||
|
bf817a14c7
|
@@ -151,9 +151,9 @@ func (app *app) setup() error {
|
|||||||
return fmt.Errorf("app: failed to start token repository: %w", err)
|
return fmt.Errorf("app: failed to start token repository: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
projectRepository, err := repository.NewProject(app.ctx, app.db, app.logger.WithGroup("repository.project"), app.assert)
|
publicationRepository, err := repository.NewPublication(app.ctx, app.db, app.logger.WithGroup("repository.publication"), app.assert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("app: failed to start project repository: %w", err)
|
return fmt.Errorf("app: failed to start publication repository: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
permissionRepository, err := repository.NewPermissions(app.ctx, app.db, app.logger.WithGroup("repository.permission"), app.assert)
|
permissionRepository, err := repository.NewPermissions(app.ctx, app.db, app.logger.WithGroup("repository.permission"), app.assert)
|
||||||
@@ -169,12 +169,12 @@ func (app *app) setup() error {
|
|||||||
Logger: app.logger.WithGroup("service.token"),
|
Logger: app.logger.WithGroup("service.token"),
|
||||||
Assertions: app.assert,
|
Assertions: app.assert,
|
||||||
})
|
})
|
||||||
projectService := service.NewProject(projectRepository, permissionRepository, app.logger.WithGroup("service.project"), app.assert)
|
publicationService := service.NewPublication(publicationRepository, permissionRepository, app.logger.WithGroup("service.publication"), app.assert)
|
||||||
|
|
||||||
app.handler, err = router.New(router.Config{
|
app.handler, err = router.New(router.Config{
|
||||||
UserService: userService,
|
UserService: userService,
|
||||||
TokenService: tokenService,
|
TokenService: tokenService,
|
||||||
ProjectService: projectService,
|
PublicationService: publicationService,
|
||||||
|
|
||||||
Templates: app.templates,
|
Templates: app.templates,
|
||||||
DisableCache: app.developmentMode,
|
DisableCache: app.developmentMode,
|
||||||
|
|||||||
3
editor/go.mod
Normal file
3
editor/go.mod
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module code.capytal.cc/capytal/comicverse/editor
|
||||||
|
|
||||||
|
go 1.25.2
|
||||||
0
editor/go.sum
Normal file
0
editor/go.sum
Normal file
1
go.work
1
go.work
@@ -2,6 +2,7 @@ go 1.25.2
|
|||||||
|
|
||||||
use (
|
use (
|
||||||
.
|
.
|
||||||
|
./editor
|
||||||
./smalltrip
|
./smalltrip
|
||||||
./x
|
./x
|
||||||
)
|
)
|
||||||
|
|||||||
8
internals/randname/COPYRIGHT
Normal file
8
internals/randname/COPYRIGHT
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Adjectives and names list files were copied from Dustin Kirkland's <dustin.kirkland@gmail.com>
|
||||||
|
petname project at Github, specifically from these files:
|
||||||
|
- https://github.com/dustinkirkland/petname/blob/5a79a2954e94ddbe6dc47b35c8b08ed931fc0e7f/usr/share/petname/small/adjectives.txt
|
||||||
|
- https://github.com/dustinkirkland/petname/blob/5a79a2954e94ddbe6dc47b35c8b08ed931fc0e7f/usr/share/petname/small/names.txt
|
||||||
|
|
||||||
|
The original files are provided and released under the Apache License version 2,
|
||||||
|
which a copy is available at
|
||||||
|
https://github.com/dustinkirkland/petname/blob/5a79a2954e94ddbe6dc47b35c8b08ed931fc0e7f/LICENSE
|
||||||
452
internals/randname/adjectives.txt
Normal file
452
internals/randname/adjectives.txt
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
ox
|
||||||
|
ant
|
||||||
|
ape
|
||||||
|
asp
|
||||||
|
bat
|
||||||
|
bee
|
||||||
|
boa
|
||||||
|
bug
|
||||||
|
cat
|
||||||
|
cod
|
||||||
|
cow
|
||||||
|
cub
|
||||||
|
doe
|
||||||
|
dog
|
||||||
|
eel
|
||||||
|
eft
|
||||||
|
elf
|
||||||
|
elk
|
||||||
|
emu
|
||||||
|
ewe
|
||||||
|
fly
|
||||||
|
fox
|
||||||
|
gar
|
||||||
|
gnu
|
||||||
|
hen
|
||||||
|
hog
|
||||||
|
imp
|
||||||
|
jay
|
||||||
|
kid
|
||||||
|
kit
|
||||||
|
koi
|
||||||
|
lab
|
||||||
|
man
|
||||||
|
owl
|
||||||
|
pig
|
||||||
|
pug
|
||||||
|
pup
|
||||||
|
ram
|
||||||
|
rat
|
||||||
|
ray
|
||||||
|
yak
|
||||||
|
bass
|
||||||
|
bear
|
||||||
|
bird
|
||||||
|
boar
|
||||||
|
buck
|
||||||
|
bull
|
||||||
|
calf
|
||||||
|
chow
|
||||||
|
clam
|
||||||
|
colt
|
||||||
|
crab
|
||||||
|
crow
|
||||||
|
dane
|
||||||
|
deer
|
||||||
|
dodo
|
||||||
|
dory
|
||||||
|
dove
|
||||||
|
drum
|
||||||
|
duck
|
||||||
|
fawn
|
||||||
|
fish
|
||||||
|
flea
|
||||||
|
foal
|
||||||
|
fowl
|
||||||
|
frog
|
||||||
|
gnat
|
||||||
|
goat
|
||||||
|
grub
|
||||||
|
gull
|
||||||
|
hare
|
||||||
|
hawk
|
||||||
|
ibex
|
||||||
|
joey
|
||||||
|
kite
|
||||||
|
kiwi
|
||||||
|
lamb
|
||||||
|
lark
|
||||||
|
lion
|
||||||
|
loon
|
||||||
|
lynx
|
||||||
|
mako
|
||||||
|
mink
|
||||||
|
mite
|
||||||
|
mole
|
||||||
|
moth
|
||||||
|
mule
|
||||||
|
mutt
|
||||||
|
newt
|
||||||
|
orca
|
||||||
|
oryx
|
||||||
|
pika
|
||||||
|
pony
|
||||||
|
puma
|
||||||
|
seal
|
||||||
|
shad
|
||||||
|
slug
|
||||||
|
sole
|
||||||
|
stag
|
||||||
|
stud
|
||||||
|
swan
|
||||||
|
tahr
|
||||||
|
teal
|
||||||
|
tick
|
||||||
|
toad
|
||||||
|
tuna
|
||||||
|
wasp
|
||||||
|
wolf
|
||||||
|
worm
|
||||||
|
wren
|
||||||
|
yeti
|
||||||
|
adder
|
||||||
|
akita
|
||||||
|
alien
|
||||||
|
aphid
|
||||||
|
bison
|
||||||
|
boxer
|
||||||
|
bream
|
||||||
|
bunny
|
||||||
|
burro
|
||||||
|
camel
|
||||||
|
chimp
|
||||||
|
civet
|
||||||
|
cobra
|
||||||
|
coral
|
||||||
|
corgi
|
||||||
|
crane
|
||||||
|
dingo
|
||||||
|
drake
|
||||||
|
eagle
|
||||||
|
egret
|
||||||
|
filly
|
||||||
|
finch
|
||||||
|
gator
|
||||||
|
gecko
|
||||||
|
ghost
|
||||||
|
ghoul
|
||||||
|
goose
|
||||||
|
guppy
|
||||||
|
heron
|
||||||
|
hippo
|
||||||
|
horse
|
||||||
|
hound
|
||||||
|
husky
|
||||||
|
hyena
|
||||||
|
koala
|
||||||
|
krill
|
||||||
|
leech
|
||||||
|
lemur
|
||||||
|
liger
|
||||||
|
llama
|
||||||
|
louse
|
||||||
|
macaw
|
||||||
|
midge
|
||||||
|
molly
|
||||||
|
moose
|
||||||
|
moray
|
||||||
|
mouse
|
||||||
|
panda
|
||||||
|
perch
|
||||||
|
prawn
|
||||||
|
quail
|
||||||
|
racer
|
||||||
|
raven
|
||||||
|
rhino
|
||||||
|
robin
|
||||||
|
satyr
|
||||||
|
shark
|
||||||
|
sheep
|
||||||
|
shrew
|
||||||
|
skink
|
||||||
|
skunk
|
||||||
|
sloth
|
||||||
|
snail
|
||||||
|
snake
|
||||||
|
snipe
|
||||||
|
squid
|
||||||
|
stork
|
||||||
|
swift
|
||||||
|
tapir
|
||||||
|
tetra
|
||||||
|
tiger
|
||||||
|
troll
|
||||||
|
trout
|
||||||
|
viper
|
||||||
|
wahoo
|
||||||
|
whale
|
||||||
|
zebra
|
||||||
|
alpaca
|
||||||
|
amoeba
|
||||||
|
baboon
|
||||||
|
badger
|
||||||
|
beagle
|
||||||
|
bedbug
|
||||||
|
beetle
|
||||||
|
bengal
|
||||||
|
bobcat
|
||||||
|
caiman
|
||||||
|
cattle
|
||||||
|
cicada
|
||||||
|
collie
|
||||||
|
condor
|
||||||
|
cougar
|
||||||
|
coyote
|
||||||
|
dassie
|
||||||
|
dragon
|
||||||
|
earwig
|
||||||
|
falcon
|
||||||
|
feline
|
||||||
|
ferret
|
||||||
|
gannet
|
||||||
|
gibbon
|
||||||
|
glider
|
||||||
|
goblin
|
||||||
|
gopher
|
||||||
|
grouse
|
||||||
|
guinea
|
||||||
|
hermit
|
||||||
|
hornet
|
||||||
|
iguana
|
||||||
|
impala
|
||||||
|
insect
|
||||||
|
jackal
|
||||||
|
jaguar
|
||||||
|
jennet
|
||||||
|
kitten
|
||||||
|
kodiak
|
||||||
|
lizard
|
||||||
|
locust
|
||||||
|
maggot
|
||||||
|
magpie
|
||||||
|
mammal
|
||||||
|
mantis
|
||||||
|
marlin
|
||||||
|
marmot
|
||||||
|
marten
|
||||||
|
martin
|
||||||
|
mayfly
|
||||||
|
minnow
|
||||||
|
monkey
|
||||||
|
mullet
|
||||||
|
muskox
|
||||||
|
ocelot
|
||||||
|
oriole
|
||||||
|
osprey
|
||||||
|
oyster
|
||||||
|
parrot
|
||||||
|
pigeon
|
||||||
|
piglet
|
||||||
|
poodle
|
||||||
|
possum
|
||||||
|
python
|
||||||
|
quagga
|
||||||
|
rabbit
|
||||||
|
raptor
|
||||||
|
rodent
|
||||||
|
roughy
|
||||||
|
salmon
|
||||||
|
sawfly
|
||||||
|
serval
|
||||||
|
shiner
|
||||||
|
shrimp
|
||||||
|
spider
|
||||||
|
sponge
|
||||||
|
tarpon
|
||||||
|
thrush
|
||||||
|
tomcat
|
||||||
|
toucan
|
||||||
|
turkey
|
||||||
|
turtle
|
||||||
|
urchin
|
||||||
|
vervet
|
||||||
|
walrus
|
||||||
|
weasel
|
||||||
|
weevil
|
||||||
|
wombat
|
||||||
|
anchovy
|
||||||
|
anemone
|
||||||
|
bluejay
|
||||||
|
buffalo
|
||||||
|
bulldog
|
||||||
|
buzzard
|
||||||
|
caribou
|
||||||
|
catfish
|
||||||
|
chamois
|
||||||
|
cheetah
|
||||||
|
chicken
|
||||||
|
chigger
|
||||||
|
cowbird
|
||||||
|
crappie
|
||||||
|
crawdad
|
||||||
|
cricket
|
||||||
|
dogfish
|
||||||
|
dolphin
|
||||||
|
firefly
|
||||||
|
garfish
|
||||||
|
gazelle
|
||||||
|
gelding
|
||||||
|
giraffe
|
||||||
|
gobbler
|
||||||
|
gorilla
|
||||||
|
goshawk
|
||||||
|
grackle
|
||||||
|
griffon
|
||||||
|
grizzly
|
||||||
|
grouper
|
||||||
|
haddock
|
||||||
|
hagfish
|
||||||
|
halibut
|
||||||
|
hamster
|
||||||
|
herring
|
||||||
|
javelin
|
||||||
|
jawfish
|
||||||
|
jaybird
|
||||||
|
katydid
|
||||||
|
ladybug
|
||||||
|
lamprey
|
||||||
|
lemming
|
||||||
|
leopard
|
||||||
|
lioness
|
||||||
|
lobster
|
||||||
|
macaque
|
||||||
|
mallard
|
||||||
|
mammoth
|
||||||
|
manatee
|
||||||
|
mastiff
|
||||||
|
meerkat
|
||||||
|
mollusk
|
||||||
|
monarch
|
||||||
|
mongrel
|
||||||
|
monitor
|
||||||
|
monster
|
||||||
|
mudfish
|
||||||
|
muskrat
|
||||||
|
mustang
|
||||||
|
narwhal
|
||||||
|
oarfish
|
||||||
|
octopus
|
||||||
|
opossum
|
||||||
|
ostrich
|
||||||
|
panther
|
||||||
|
peacock
|
||||||
|
pegasus
|
||||||
|
pelican
|
||||||
|
penguin
|
||||||
|
phoenix
|
||||||
|
piranha
|
||||||
|
polecat
|
||||||
|
primate
|
||||||
|
quetzal
|
||||||
|
raccoon
|
||||||
|
rattler
|
||||||
|
redbird
|
||||||
|
redfish
|
||||||
|
reptile
|
||||||
|
rooster
|
||||||
|
sawfish
|
||||||
|
sculpin
|
||||||
|
seagull
|
||||||
|
skylark
|
||||||
|
snapper
|
||||||
|
spaniel
|
||||||
|
sparrow
|
||||||
|
sunbeam
|
||||||
|
sunbird
|
||||||
|
sunfish
|
||||||
|
tadpole
|
||||||
|
terrier
|
||||||
|
unicorn
|
||||||
|
vulture
|
||||||
|
wallaby
|
||||||
|
walleye
|
||||||
|
warthog
|
||||||
|
whippet
|
||||||
|
wildcat
|
||||||
|
aardvark
|
||||||
|
airedale
|
||||||
|
albacore
|
||||||
|
anteater
|
||||||
|
antelope
|
||||||
|
arachnid
|
||||||
|
barnacle
|
||||||
|
basilisk
|
||||||
|
blowfish
|
||||||
|
bluebird
|
||||||
|
bluegill
|
||||||
|
bonefish
|
||||||
|
bullfrog
|
||||||
|
cardinal
|
||||||
|
chipmunk
|
||||||
|
cockatoo
|
||||||
|
crayfish
|
||||||
|
dinosaur
|
||||||
|
doberman
|
||||||
|
duckling
|
||||||
|
elephant
|
||||||
|
escargot
|
||||||
|
flamingo
|
||||||
|
flounder
|
||||||
|
foxhound
|
||||||
|
glowworm
|
||||||
|
goldfish
|
||||||
|
grubworm
|
||||||
|
hedgehog
|
||||||
|
honeybee
|
||||||
|
hookworm
|
||||||
|
humpback
|
||||||
|
kangaroo
|
||||||
|
killdeer
|
||||||
|
kingfish
|
||||||
|
labrador
|
||||||
|
lacewing
|
||||||
|
ladybird
|
||||||
|
lionfish
|
||||||
|
longhorn
|
||||||
|
mackerel
|
||||||
|
malamute
|
||||||
|
marmoset
|
||||||
|
mastodon
|
||||||
|
moccasin
|
||||||
|
mongoose
|
||||||
|
monkfish
|
||||||
|
mosquito
|
||||||
|
pangolin
|
||||||
|
parakeet
|
||||||
|
pheasant
|
||||||
|
pipefish
|
||||||
|
platypus
|
||||||
|
polliwog
|
||||||
|
porpoise
|
||||||
|
reindeer
|
||||||
|
ringtail
|
||||||
|
sailfish
|
||||||
|
scorpion
|
||||||
|
seahorse
|
||||||
|
seasnail
|
||||||
|
sheepdog
|
||||||
|
shepherd
|
||||||
|
silkworm
|
||||||
|
squirrel
|
||||||
|
stallion
|
||||||
|
starfish
|
||||||
|
starling
|
||||||
|
stingray
|
||||||
|
stinkbug
|
||||||
|
sturgeon
|
||||||
|
terrapin
|
||||||
|
titmouse
|
||||||
|
tortoise
|
||||||
|
treefrog
|
||||||
|
werewolf
|
||||||
|
woodcock
|
||||||
449
internals/randname/names.txt
Normal file
449
internals/randname/names.txt
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
able
|
||||||
|
above
|
||||||
|
absolute
|
||||||
|
accepted
|
||||||
|
accurate
|
||||||
|
ace
|
||||||
|
active
|
||||||
|
actual
|
||||||
|
adapted
|
||||||
|
adapting
|
||||||
|
adequate
|
||||||
|
adjusted
|
||||||
|
advanced
|
||||||
|
alert
|
||||||
|
alive
|
||||||
|
allowed
|
||||||
|
allowing
|
||||||
|
amazed
|
||||||
|
amazing
|
||||||
|
ample
|
||||||
|
amused
|
||||||
|
amusing
|
||||||
|
apparent
|
||||||
|
apt
|
||||||
|
arriving
|
||||||
|
artistic
|
||||||
|
assured
|
||||||
|
assuring
|
||||||
|
awaited
|
||||||
|
awake
|
||||||
|
aware
|
||||||
|
balanced
|
||||||
|
becoming
|
||||||
|
beloved
|
||||||
|
better
|
||||||
|
big
|
||||||
|
blessed
|
||||||
|
bold
|
||||||
|
boss
|
||||||
|
brave
|
||||||
|
brief
|
||||||
|
bright
|
||||||
|
bursting
|
||||||
|
busy
|
||||||
|
calm
|
||||||
|
capable
|
||||||
|
capital
|
||||||
|
careful
|
||||||
|
caring
|
||||||
|
casual
|
||||||
|
causal
|
||||||
|
central
|
||||||
|
certain
|
||||||
|
champion
|
||||||
|
charmed
|
||||||
|
charming
|
||||||
|
cheerful
|
||||||
|
chief
|
||||||
|
choice
|
||||||
|
civil
|
||||||
|
classic
|
||||||
|
clean
|
||||||
|
clear
|
||||||
|
clever
|
||||||
|
climbing
|
||||||
|
close
|
||||||
|
closing
|
||||||
|
coherent
|
||||||
|
comic
|
||||||
|
communal
|
||||||
|
complete
|
||||||
|
composed
|
||||||
|
concise
|
||||||
|
concrete
|
||||||
|
content
|
||||||
|
cool
|
||||||
|
correct
|
||||||
|
cosmic
|
||||||
|
crack
|
||||||
|
creative
|
||||||
|
credible
|
||||||
|
crisp
|
||||||
|
crucial
|
||||||
|
cuddly
|
||||||
|
cunning
|
||||||
|
curious
|
||||||
|
current
|
||||||
|
cute
|
||||||
|
daring
|
||||||
|
darling
|
||||||
|
dashing
|
||||||
|
dear
|
||||||
|
decent
|
||||||
|
deciding
|
||||||
|
deep
|
||||||
|
definite
|
||||||
|
delicate
|
||||||
|
desired
|
||||||
|
destined
|
||||||
|
devoted
|
||||||
|
direct
|
||||||
|
discrete
|
||||||
|
distinct
|
||||||
|
diverse
|
||||||
|
divine
|
||||||
|
dominant
|
||||||
|
driven
|
||||||
|
driving
|
||||||
|
dynamic
|
||||||
|
eager
|
||||||
|
easy
|
||||||
|
electric
|
||||||
|
elegant
|
||||||
|
emerging
|
||||||
|
eminent
|
||||||
|
enabled
|
||||||
|
enabling
|
||||||
|
endless
|
||||||
|
engaged
|
||||||
|
engaging
|
||||||
|
enhanced
|
||||||
|
enjoyed
|
||||||
|
enormous
|
||||||
|
enough
|
||||||
|
epic
|
||||||
|
equal
|
||||||
|
equipped
|
||||||
|
eternal
|
||||||
|
ethical
|
||||||
|
evident
|
||||||
|
evolved
|
||||||
|
evolving
|
||||||
|
exact
|
||||||
|
excited
|
||||||
|
exciting
|
||||||
|
exotic
|
||||||
|
expert
|
||||||
|
factual
|
||||||
|
fair
|
||||||
|
faithful
|
||||||
|
famous
|
||||||
|
fancy
|
||||||
|
fast
|
||||||
|
feasible
|
||||||
|
fine
|
||||||
|
finer
|
||||||
|
firm
|
||||||
|
first
|
||||||
|
fit
|
||||||
|
fitting
|
||||||
|
fleet
|
||||||
|
flexible
|
||||||
|
flowing
|
||||||
|
fluent
|
||||||
|
flying
|
||||||
|
fond
|
||||||
|
frank
|
||||||
|
free
|
||||||
|
fresh
|
||||||
|
full
|
||||||
|
fun
|
||||||
|
funky
|
||||||
|
funny
|
||||||
|
game
|
||||||
|
generous
|
||||||
|
gentle
|
||||||
|
genuine
|
||||||
|
giving
|
||||||
|
glad
|
||||||
|
glorious
|
||||||
|
glowing
|
||||||
|
golden
|
||||||
|
good
|
||||||
|
gorgeous
|
||||||
|
grand
|
||||||
|
grateful
|
||||||
|
great
|
||||||
|
growing
|
||||||
|
grown
|
||||||
|
guided
|
||||||
|
guiding
|
||||||
|
handy
|
||||||
|
happy
|
||||||
|
hardy
|
||||||
|
harmless
|
||||||
|
healthy
|
||||||
|
helped
|
||||||
|
helpful
|
||||||
|
helping
|
||||||
|
heroic
|
||||||
|
hip
|
||||||
|
holy
|
||||||
|
honest
|
||||||
|
hopeful
|
||||||
|
hot
|
||||||
|
huge
|
||||||
|
humane
|
||||||
|
humble
|
||||||
|
humorous
|
||||||
|
ideal
|
||||||
|
immense
|
||||||
|
immortal
|
||||||
|
immune
|
||||||
|
improved
|
||||||
|
in
|
||||||
|
included
|
||||||
|
infinite
|
||||||
|
informed
|
||||||
|
innocent
|
||||||
|
inspired
|
||||||
|
integral
|
||||||
|
intense
|
||||||
|
intent
|
||||||
|
internal
|
||||||
|
intimate
|
||||||
|
inviting
|
||||||
|
joint
|
||||||
|
just
|
||||||
|
keen
|
||||||
|
key
|
||||||
|
kind
|
||||||
|
knowing
|
||||||
|
known
|
||||||
|
large
|
||||||
|
lasting
|
||||||
|
leading
|
||||||
|
learning
|
||||||
|
legal
|
||||||
|
legible
|
||||||
|
lenient
|
||||||
|
liberal
|
||||||
|
light
|
||||||
|
liked
|
||||||
|
literate
|
||||||
|
live
|
||||||
|
living
|
||||||
|
logical
|
||||||
|
loved
|
||||||
|
loving
|
||||||
|
loyal
|
||||||
|
lucky
|
||||||
|
magical
|
||||||
|
magnetic
|
||||||
|
main
|
||||||
|
major
|
||||||
|
many
|
||||||
|
massive
|
||||||
|
master
|
||||||
|
mature
|
||||||
|
maximum
|
||||||
|
measured
|
||||||
|
meet
|
||||||
|
merry
|
||||||
|
mighty
|
||||||
|
mint
|
||||||
|
model
|
||||||
|
modern
|
||||||
|
modest
|
||||||
|
moral
|
||||||
|
more
|
||||||
|
moved
|
||||||
|
moving
|
||||||
|
musical
|
||||||
|
mutual
|
||||||
|
national
|
||||||
|
native
|
||||||
|
natural
|
||||||
|
nearby
|
||||||
|
neat
|
||||||
|
needed
|
||||||
|
neutral
|
||||||
|
new
|
||||||
|
next
|
||||||
|
nice
|
||||||
|
noble
|
||||||
|
normal
|
||||||
|
notable
|
||||||
|
noted
|
||||||
|
novel
|
||||||
|
obliging
|
||||||
|
on
|
||||||
|
one
|
||||||
|
open
|
||||||
|
optimal
|
||||||
|
optimum
|
||||||
|
organic
|
||||||
|
oriented
|
||||||
|
outgoing
|
||||||
|
patient
|
||||||
|
peaceful
|
||||||
|
perfect
|
||||||
|
pet
|
||||||
|
picked
|
||||||
|
pleasant
|
||||||
|
pleased
|
||||||
|
pleasing
|
||||||
|
poetic
|
||||||
|
polished
|
||||||
|
polite
|
||||||
|
popular
|
||||||
|
positive
|
||||||
|
possible
|
||||||
|
powerful
|
||||||
|
precious
|
||||||
|
precise
|
||||||
|
premium
|
||||||
|
prepared
|
||||||
|
present
|
||||||
|
pretty
|
||||||
|
primary
|
||||||
|
prime
|
||||||
|
pro
|
||||||
|
probable
|
||||||
|
profound
|
||||||
|
promoted
|
||||||
|
prompt
|
||||||
|
proper
|
||||||
|
proud
|
||||||
|
proven
|
||||||
|
pumped
|
||||||
|
pure
|
||||||
|
quality
|
||||||
|
quick
|
||||||
|
quiet
|
||||||
|
rapid
|
||||||
|
rare
|
||||||
|
rational
|
||||||
|
ready
|
||||||
|
real
|
||||||
|
refined
|
||||||
|
regular
|
||||||
|
related
|
||||||
|
relative
|
||||||
|
relaxed
|
||||||
|
relaxing
|
||||||
|
relevant
|
||||||
|
relieved
|
||||||
|
renewed
|
||||||
|
renewing
|
||||||
|
resolved
|
||||||
|
rested
|
||||||
|
rich
|
||||||
|
right
|
||||||
|
robust
|
||||||
|
romantic
|
||||||
|
ruling
|
||||||
|
sacred
|
||||||
|
safe
|
||||||
|
saved
|
||||||
|
saving
|
||||||
|
secure
|
||||||
|
select
|
||||||
|
selected
|
||||||
|
sensible
|
||||||
|
set
|
||||||
|
settled
|
||||||
|
settling
|
||||||
|
sharing
|
||||||
|
sharp
|
||||||
|
shining
|
||||||
|
simple
|
||||||
|
sincere
|
||||||
|
singular
|
||||||
|
skilled
|
||||||
|
smart
|
||||||
|
smashing
|
||||||
|
smiling
|
||||||
|
smooth
|
||||||
|
social
|
||||||
|
solid
|
||||||
|
sought
|
||||||
|
sound
|
||||||
|
special
|
||||||
|
splendid
|
||||||
|
square
|
||||||
|
stable
|
||||||
|
star
|
||||||
|
steady
|
||||||
|
sterling
|
||||||
|
still
|
||||||
|
stirred
|
||||||
|
stirring
|
||||||
|
striking
|
||||||
|
strong
|
||||||
|
stunning
|
||||||
|
subtle
|
||||||
|
suitable
|
||||||
|
suited
|
||||||
|
summary
|
||||||
|
sunny
|
||||||
|
super
|
||||||
|
superb
|
||||||
|
supreme
|
||||||
|
sure
|
||||||
|
sweeping
|
||||||
|
sweet
|
||||||
|
talented
|
||||||
|
teaching
|
||||||
|
tender
|
||||||
|
thankful
|
||||||
|
thorough
|
||||||
|
tidy
|
||||||
|
tight
|
||||||
|
together
|
||||||
|
tolerant
|
||||||
|
top
|
||||||
|
topical
|
||||||
|
tops
|
||||||
|
touched
|
||||||
|
touching
|
||||||
|
tough
|
||||||
|
true
|
||||||
|
trusted
|
||||||
|
trusting
|
||||||
|
trusty
|
||||||
|
ultimate
|
||||||
|
unbiased
|
||||||
|
uncommon
|
||||||
|
unified
|
||||||
|
unique
|
||||||
|
united
|
||||||
|
up
|
||||||
|
upright
|
||||||
|
upward
|
||||||
|
usable
|
||||||
|
useful
|
||||||
|
valid
|
||||||
|
valued
|
||||||
|
vast
|
||||||
|
verified
|
||||||
|
viable
|
||||||
|
vital
|
||||||
|
vocal
|
||||||
|
wanted
|
||||||
|
warm
|
||||||
|
wealthy
|
||||||
|
welcome
|
||||||
|
welcomed
|
||||||
|
well
|
||||||
|
whole
|
||||||
|
willing
|
||||||
|
winning
|
||||||
|
wired
|
||||||
|
wise
|
||||||
|
witty
|
||||||
|
wondrous
|
||||||
|
workable
|
||||||
|
working
|
||||||
|
worthy
|
||||||
33
internals/randname/randname.go
Normal file
33
internals/randname/randname.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package randname
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Make generator be based on fantasy, sci-fi and other literature
|
||||||
|
// and artistic names.
|
||||||
|
|
||||||
|
//go:embed adjectives.txt
|
||||||
|
var adjectives string
|
||||||
|
|
||||||
|
//go:embed names.txt
|
||||||
|
var names string
|
||||||
|
|
||||||
|
var (
|
||||||
|
adjectivesList = strings.Split(adjectives, "\n")
|
||||||
|
namesList = strings.Split(names, "\n")
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(sep ...string) string {
|
||||||
|
if len(sep) == 0 {
|
||||||
|
sep = append(sep, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
a := adjectivesList[rand.Intn(len(adjectivesList))]
|
||||||
|
n := namesList[rand.Intn(len(namesList))]
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s%s%s", a, sep[0], n)
|
||||||
|
}
|
||||||
@@ -120,7 +120,7 @@ const (
|
|||||||
PermissionAuthor Permissions = 0x1111111111111111 // "author"
|
PermissionAuthor Permissions = 0x1111111111111111 // "author"
|
||||||
PermissionAdminDelete Permissions = 0x1000000000000000 // "admin.delete" -----
|
PermissionAdminDelete Permissions = 0x1000000000000000 // "admin.delete" -----
|
||||||
PermissionAdminAll Permissions = 0x0111110000000001 // "admin.all"
|
PermissionAdminAll Permissions = 0x0111110000000001 // "admin.all"
|
||||||
PermissionAdminProject Permissions = 0x0100000000000000 // "admin.project"
|
PermissionAdminPublication Permissions = 0x0100000000000000 // "admin.publication"
|
||||||
PermissionAdminMembers Permissions = 0x0010000000000000 // "admin.members"
|
PermissionAdminMembers Permissions = 0x0010000000000000 // "admin.members"
|
||||||
PermissionEditAll Permissions = 0x0000001111111111 // "edit.all" ---------
|
PermissionEditAll Permissions = 0x0000001111111111 // "edit.all" ---------
|
||||||
PermissionEditPages Permissions = 0x0000000100000000 // "edit.pages"
|
PermissionEditPages Permissions = 0x0000000100000000 // "edit.pages"
|
||||||
@@ -134,7 +134,7 @@ const (
|
|||||||
var PermissionLabels = map[Permissions]string{
|
var PermissionLabels = map[Permissions]string{
|
||||||
PermissionAuthor: "author",
|
PermissionAuthor: "author",
|
||||||
PermissionAdminDelete: "admin.delete",
|
PermissionAdminDelete: "admin.delete",
|
||||||
PermissionAdminProject: "admin.project",
|
PermissionAdminPublication: "admin.publication",
|
||||||
PermissionAdminMembers: "admin.members",
|
PermissionAdminMembers: "admin.members",
|
||||||
PermissionEditPages: "edit.pages",
|
PermissionEditPages: "edit.pages",
|
||||||
PermissionEditInteractions: "edit.interactions",
|
PermissionEditInteractions: "edit.interactions",
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Project struct {
|
type Publication struct {
|
||||||
ID uuid.UUID // Must be unique, represented as base64 string in URLs
|
ID uuid.UUID // Must be unique, represented as base64 string in URLs
|
||||||
Title string // Must not be empty
|
Title string // Must not be empty
|
||||||
DateCreated time.Time
|
DateCreated time.Time
|
||||||
DateUpdated time.Time
|
DateUpdated time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Model = (*Project)(nil)
|
var _ Model = (*Publication)(nil)
|
||||||
|
|
||||||
func (p Project) Validate() error {
|
func (p Publication) Validate() error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
if len(p.ID) == 0 {
|
if len(p.ID) == 0 {
|
||||||
errs = append(errs, ErrZeroValue{Name: "UUID"})
|
errs = append(errs, ErrZeroValue{Name: "UUID"})
|
||||||
@@ -31,7 +31,7 @@ func (p Project) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return ErrInvalidModel{Name: "Project", Errors: errs}
|
return ErrInvalidModel{Name: "Publication", Errors: errs}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -17,7 +17,7 @@ type Permissions struct {
|
|||||||
baseRepostiory
|
baseRepostiory
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be initiated after [User] and [Project]
|
// Must be initiated after [User] and [Publication]
|
||||||
func NewPermissions(
|
func NewPermissions(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
db *sql.DB,
|
db *sql.DB,
|
||||||
@@ -32,17 +32,17 @@ func NewPermissions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := fmt.Sprintf(`
|
q := fmt.Sprintf(`
|
||||||
CREATE TABLE IF NOT EXISTS project_permissions (
|
CREATE TABLE IF NOT EXISTS publication_permissions (
|
||||||
project_id TEXT NOT NULL,
|
publication_id TEXT NOT NULL,
|
||||||
user_id TEXT NOT NULL,
|
user_id TEXT NOT NULL,
|
||||||
permissions_value INTEGER NOT NULL DEFAULT '0',
|
permissions_value INTEGER NOT NULL DEFAULT '0',
|
||||||
_permissions_text TEXT NOT NULL DEFAULT '', -- For display purposes only, may not always be up-to-date
|
_permissions_text TEXT NOT NULL DEFAULT '', -- For display purposes only, may not always be up-to-date
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
updated_at TEXT NOT NULL,
|
updated_at TEXT NOT NULL,
|
||||||
|
|
||||||
PRIMARY KEY(project_id, user_id)
|
PRIMARY KEY(publication_id, user_id)
|
||||||
FOREIGN KEY(project_id)
|
FOREIGN KEY(publication_id)
|
||||||
REFERENCES projects (id)
|
REFERENCES publications (id)
|
||||||
ON DELETE CASCADE
|
ON DELETE CASCADE
|
||||||
ON UPDATE RESTRICT,
|
ON UPDATE RESTRICT,
|
||||||
FOREIGN KEY(user_id)
|
FOREIGN KEY(user_id)
|
||||||
@@ -58,13 +58,13 @@ func NewPermissions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return nil, errors.Join(errors.New("unable to create project tables"), err)
|
return nil, errors.Join(errors.New("unable to create publication tables"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Permissions{baseRepostiory: b}, nil
|
return &Permissions{baseRepostiory: b}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo Permissions) Create(project, user uuid.UUID, permissions model.Permissions) error {
|
func (repo Permissions) Create(publication, user uuid.UUID, permissions model.Permissions) error {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
@@ -75,21 +75,21 @@ func (repo Permissions) Create(project, user uuid.UUID, permissions model.Permis
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := `
|
q := `
|
||||||
INSERT INTO project_permissions (project_id, user_id, permissions_value, _permissions_text, created_at, updated_at)
|
INSERT INTO publication_permissions (publication_id, user_id, permissions_value, _permissions_text, created_at, updated_at)
|
||||||
VALUES (:project_id, :user_id, :permissions_value, :permissions_text, :created_at, :updated_at)
|
VALUES (:publication_id, :user_id, :permissions_value, :permissions_text, :created_at, :updated_at)
|
||||||
`
|
`
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
log := repo.log.With(slog.String("project_id", project.String()),
|
log := repo.log.With(slog.String("publication_id", publication.String()),
|
||||||
slog.String("user_id", user.String()),
|
slog.String("user_id", user.String()),
|
||||||
slog.String("permissions", fmt.Sprintf("%d", permissions)),
|
slog.String("permissions", fmt.Sprintf("%d", permissions)),
|
||||||
slog.String("permissions_text", permissions.String()),
|
slog.String("permissions_text", permissions.String()),
|
||||||
slog.String("query", q))
|
slog.String("query", q))
|
||||||
log.DebugContext(repo.ctx, "Inserting new project permissions")
|
log.DebugContext(repo.ctx, "Inserting new publication permissions")
|
||||||
|
|
||||||
_, err = tx.ExecContext(repo.ctx, q,
|
_, err = tx.ExecContext(repo.ctx, q,
|
||||||
sql.Named("project_id", project),
|
sql.Named("publication_id", publication),
|
||||||
sql.Named("user_id", user),
|
sql.Named("user_id", user),
|
||||||
sql.Named("permissions_value", permissions),
|
sql.Named("permissions_value", permissions),
|
||||||
sql.Named("permissions_text", permissions.String()),
|
sql.Named("permissions_text", permissions.String()),
|
||||||
@@ -97,7 +97,7 @@ func (repo Permissions) Create(project, user uuid.UUID, permissions model.Permis
|
|||||||
sql.Named("updated_at", now.Format(dateFormat)),
|
sql.Named("updated_at", now.Format(dateFormat)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to insert project permissions", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to insert publication permissions", slog.String("error", err.Error()))
|
||||||
return errors.Join(ErrExecuteQuery, err)
|
return errors.Join(ErrExecuteQuery, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,24 +109,24 @@ func (repo Permissions) Create(project, user uuid.UUID, permissions model.Permis
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo Permissions) GetByID(project uuid.UUID, user uuid.UUID) (model.Permissions, error) {
|
func (repo Permissions) GetByID(publication uuid.UUID, user uuid.UUID) (model.Permissions, error) {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
repo.assert.NotNil(repo.log)
|
repo.assert.NotNil(repo.log)
|
||||||
|
|
||||||
q := `
|
q := `
|
||||||
SELECT permissions_value FROM project_permissions
|
SELECT permissions_value FROM publication_permissions
|
||||||
WHERE project_id = :project_id
|
WHERE publication_id = :publication_id
|
||||||
AND user_id = :user_id
|
AND user_id = :user_id
|
||||||
`
|
`
|
||||||
|
|
||||||
log := repo.log.With(slog.String("projcet_id", project.String()),
|
log := repo.log.With(slog.String("projcet_id", publication.String()),
|
||||||
slog.String("user_id", user.String()),
|
slog.String("user_id", user.String()),
|
||||||
slog.String("query", q))
|
slog.String("query", q))
|
||||||
log.DebugContext(repo.ctx, "Getting by ID")
|
log.DebugContext(repo.ctx, "Getting by ID")
|
||||||
|
|
||||||
row := repo.db.QueryRowContext(repo.ctx, q,
|
row := repo.db.QueryRowContext(repo.ctx, q,
|
||||||
sql.Named("project_id", user),
|
sql.Named("publication_id", user),
|
||||||
sql.Named("user_id", user))
|
sql.Named("user_id", user))
|
||||||
|
|
||||||
var p model.Permissions
|
var p model.Permissions
|
||||||
@@ -138,7 +138,7 @@ func (repo Permissions) GetByID(project uuid.UUID, user uuid.UUID) (model.Permis
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByUserID returns a project_id-to-permissions map containing all projects and permissions that said userID
|
// GetByUserID returns a publication_id-to-permissions map containing all publications and permissions that said userID
|
||||||
// has relation to.
|
// has relation to.
|
||||||
func (repo Permissions) GetByUserID(user uuid.UUID) (permissions map[uuid.UUID]model.Permissions, err error) {
|
func (repo Permissions) GetByUserID(user uuid.UUID) (permissions map[uuid.UUID]model.Permissions, err error) {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
@@ -152,7 +152,7 @@ func (repo Permissions) GetByUserID(user uuid.UUID) (permissions map[uuid.UUID]m
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := `
|
q := `
|
||||||
SELECT project_id, permissions_value FROM project_permissions
|
SELECT publication_id, permissions_value FROM publication_permissions
|
||||||
WHERE user_id = :user_id
|
WHERE user_id = :user_id
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -176,16 +176,16 @@ func (repo Permissions) GetByUserID(user uuid.UUID) (permissions map[uuid.UUID]m
|
|||||||
ps := map[uuid.UUID]model.Permissions{}
|
ps := map[uuid.UUID]model.Permissions{}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var project uuid.UUID
|
var publication uuid.UUID
|
||||||
var permissions model.Permissions
|
var permissions model.Permissions
|
||||||
|
|
||||||
err := rows.Scan(&project, &permissions)
|
err := rows.Scan(&publication, &permissions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to scan permissions of user id", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to scan permissions of user id", slog.String("error", err.Error()))
|
||||||
return nil, errors.Join(ErrInvalidOutput, err)
|
return nil, errors.Join(ErrInvalidOutput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ps[project] = permissions
|
ps[publication] = permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
@@ -196,7 +196,7 @@ func (repo Permissions) GetByUserID(user uuid.UUID) (permissions map[uuid.UUID]m
|
|||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo Permissions) Update(project, user uuid.UUID, permissions model.Permissions) error {
|
func (repo Permissions) Update(publication, user uuid.UUID, permissions model.Permissions) error {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
repo.assert.NotNil(repo.log)
|
repo.assert.NotNil(repo.log)
|
||||||
@@ -207,20 +207,20 @@ func (repo Permissions) Update(project, user uuid.UUID, permissions model.Permis
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := `
|
q := `
|
||||||
UPDATE project_permissions
|
UPDATE publication_permissions
|
||||||
SET permissions_value = :permissions_value
|
SET permissions_value = :permissions_value
|
||||||
_permissions_text = :permissions_text
|
_permissions_text = :permissions_text
|
||||||
updated_at = :updated_at
|
updated_at = :updated_at
|
||||||
WHERE project_uuid = :project_uuid
|
WHERE publication_uuid = :publication_uuid
|
||||||
AND user_uuid = :user_uuid
|
AND user_uuid = :user_uuid
|
||||||
`
|
`
|
||||||
|
|
||||||
log := repo.log.With(slog.String("project_id", project.String()),
|
log := repo.log.With(slog.String("publication_id", publication.String()),
|
||||||
slog.String("user_id", user.String()),
|
slog.String("user_id", user.String()),
|
||||||
slog.String("permissions", fmt.Sprintf("%d", permissions)),
|
slog.String("permissions", fmt.Sprintf("%d", permissions)),
|
||||||
slog.String("permissions_text", permissions.String()),
|
slog.String("permissions_text", permissions.String()),
|
||||||
slog.String("query", q))
|
slog.String("query", q))
|
||||||
log.DebugContext(repo.ctx, "Updating project permissions")
|
log.DebugContext(repo.ctx, "Updating publication permissions")
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
@@ -228,11 +228,11 @@ func (repo Permissions) Update(project, user uuid.UUID, permissions model.Permis
|
|||||||
sql.Named("permissions_value", permissions),
|
sql.Named("permissions_value", permissions),
|
||||||
sql.Named("permissions_text", permissions.String()),
|
sql.Named("permissions_text", permissions.String()),
|
||||||
sql.Named("updated_at", now.Format(dateFormat)),
|
sql.Named("updated_at", now.Format(dateFormat)),
|
||||||
sql.Named("project_id", project),
|
sql.Named("publication_id", publication),
|
||||||
sql.Named("user_id", user),
|
sql.Named("user_id", user),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to update project permissions", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to update publication permissions", slog.String("error", err.Error()))
|
||||||
return errors.Join(ErrExecuteQuery, err)
|
return errors.Join(ErrExecuteQuery, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ func (repo Permissions) Update(project, user uuid.UUID, permissions model.Permis
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo Permissions) Delete(project, user uuid.UUID) error {
|
func (repo Permissions) Delete(publication, user uuid.UUID) error {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
@@ -255,22 +255,22 @@ func (repo Permissions) Delete(project, user uuid.UUID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := `
|
q := `
|
||||||
DELETE FROM project_permissions
|
DELETE FROM publication_permissions
|
||||||
WHERE project_id = :project_id
|
WHERE publication_id = :publication_id
|
||||||
AND user_id = :user_id
|
AND user_id = :user_id
|
||||||
`
|
`
|
||||||
|
|
||||||
log := repo.log.With(slog.String("project_id", project.String()),
|
log := repo.log.With(slog.String("publication_id", publication.String()),
|
||||||
slog.String("user_id", user.String()),
|
slog.String("user_id", user.String()),
|
||||||
slog.String("query", q))
|
slog.String("query", q))
|
||||||
log.DebugContext(repo.ctx, "Deleting project permissions")
|
log.DebugContext(repo.ctx, "Deleting publication permissions")
|
||||||
|
|
||||||
_, err = tx.ExecContext(repo.ctx, q,
|
_, err = tx.ExecContext(repo.ctx, q,
|
||||||
sql.Named("project_id", project),
|
sql.Named("publication_id", publication),
|
||||||
sql.Named("user_id", user),
|
sql.Named("user_id", user),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to delete project permissions", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to delete publication permissions", slog.String("error", err.Error()))
|
||||||
return errors.Join(ErrExecuteQuery, err)
|
return errors.Join(ErrExecuteQuery, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Project struct {
|
type Publication struct {
|
||||||
baseRepostiory
|
baseRepostiory
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProject(ctx context.Context, db *sql.DB, log *slog.Logger, assert tinyssert.Assertions) (*Project, error) {
|
func NewPublication(ctx context.Context, db *sql.DB, log *slog.Logger, assert tinyssert.Assertions) (*Publication, error) {
|
||||||
b := newBaseRepostiory(ctx, db, log, assert)
|
b := newBaseRepostiory(ctx, db, log, assert)
|
||||||
|
|
||||||
tx, err := db.BeginTx(ctx, nil)
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
@@ -27,7 +27,7 @@ func NewProject(ctx context.Context, db *sql.DB, log *slog.Logger, assert tinyss
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
_, err = tx.ExecContext(ctx, `
|
||||||
CREATE TABLE IF NOT EXISTS projects (
|
CREATE TABLE IF NOT EXISTS publications (
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
@@ -38,13 +38,13 @@ func NewProject(ctx context.Context, db *sql.DB, log *slog.Logger, assert tinyss
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return nil, errors.Join(errors.New("unable to create project tables"), err)
|
return nil, errors.Join(errors.New("unable to create publication tables"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Project{baseRepostiory: b}, nil
|
return &Publication{baseRepostiory: b}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo Project) Create(p model.Project) error {
|
func (repo Publication) Create(p model.Publication) error {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
@@ -59,12 +59,12 @@ func (repo Project) Create(p model.Project) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := `
|
q := `
|
||||||
INSERT INTO projects (id, title, created_at, updated_at)
|
INSERT INTO publications (id, title, created_at, updated_at)
|
||||||
VALUES (:id, :title, :created_at, :updated_at)
|
VALUES (:id, :title, :created_at, :updated_at)
|
||||||
`
|
`
|
||||||
|
|
||||||
log := repo.log.With(slog.String("id", p.ID.String()), slog.String("query", q))
|
log := repo.log.With(slog.String("id", p.ID.String()), slog.String("query", q))
|
||||||
log.DebugContext(repo.ctx, "Inserting new project")
|
log.DebugContext(repo.ctx, "Inserting new publication")
|
||||||
|
|
||||||
_, err = tx.ExecContext(repo.ctx, q,
|
_, err = tx.ExecContext(repo.ctx, q,
|
||||||
sql.Named("id", p.ID),
|
sql.Named("id", p.ID),
|
||||||
@@ -73,7 +73,7 @@ func (repo Project) Create(p model.Project) error {
|
|||||||
sql.Named("updated_at", p.DateUpdated.Format(dateFormat)),
|
sql.Named("updated_at", p.DateUpdated.Format(dateFormat)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to insert project", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to insert publication", slog.String("error", err.Error()))
|
||||||
return errors.Join(ErrExecuteQuery, err)
|
return errors.Join(ErrExecuteQuery, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,20 +85,20 @@ func (repo Project) Create(p model.Project) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo Project) GetByID(projectID uuid.UUID) (project model.Project, err error) {
|
func (repo Publication) GetByID(publicationID uuid.UUID) (publication model.Publication, err error) {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
repo.assert.NotNil(repo.log)
|
repo.assert.NotNil(repo.log)
|
||||||
|
|
||||||
q := `
|
q := `
|
||||||
SELECT id, title, created_at, updated_at FROM projects
|
SELECT id, title, created_at, updated_at FROM publications
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
`
|
`
|
||||||
|
|
||||||
log := repo.log.With(slog.String("query", q), slog.String("id", projectID.String()))
|
log := repo.log.With(slog.String("query", q), slog.String("id", publicationID.String()))
|
||||||
log.DebugContext(repo.ctx, "Getting project by ID")
|
log.DebugContext(repo.ctx, "Getting publication by ID")
|
||||||
|
|
||||||
row := repo.db.QueryRowContext(repo.ctx, q, sql.Named("id", projectID))
|
row := repo.db.QueryRowContext(repo.ctx, q, sql.Named("id", publicationID))
|
||||||
|
|
||||||
var id uuid.UUID
|
var id uuid.UUID
|
||||||
var title string
|
var title string
|
||||||
@@ -106,23 +106,23 @@ func (repo Project) GetByID(projectID uuid.UUID) (project model.Project, err err
|
|||||||
|
|
||||||
err = row.Scan(&id, &title, &dateCreatedStr, &dateUpdatedStr)
|
err = row.Scan(&id, &title, &dateCreatedStr, &dateUpdatedStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to scan projects with IDs", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
||||||
return model.Project{}, errors.Join(ErrInvalidOutput, err)
|
return model.Publication{}, errors.Join(ErrInvalidOutput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dateCreated, err := time.Parse(dateFormat, dateCreatedStr)
|
dateCreated, err := time.Parse(dateFormat, dateCreatedStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to scan projects with IDs", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
||||||
return model.Project{}, errors.Join(ErrInvalidOutput, err)
|
return model.Publication{}, errors.Join(ErrInvalidOutput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dateUpdated, err := time.Parse(dateFormat, dateUpdatedStr)
|
dateUpdated, err := time.Parse(dateFormat, dateUpdatedStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to scan projects with IDs", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
||||||
return model.Project{}, errors.Join(ErrInvalidOutput, err)
|
return model.Publication{}, errors.Join(ErrInvalidOutput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return model.Project{
|
return model.Publication{
|
||||||
ID: id,
|
ID: id,
|
||||||
Title: title,
|
Title: title,
|
||||||
DateCreated: dateCreated,
|
DateCreated: dateCreated,
|
||||||
@@ -130,7 +130,7 @@ func (repo Project) GetByID(projectID uuid.UUID) (project model.Project, err err
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo Project) GetByIDs(ids []uuid.UUID) (projects []model.Project, err error) {
|
func (repo Publication) GetByIDs(ids []uuid.UUID) (publications []model.Publication, err error) {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
repo.assert.NotNil(repo.log)
|
repo.assert.NotNil(repo.log)
|
||||||
@@ -147,16 +147,16 @@ func (repo Project) GetByIDs(ids []uuid.UUID) (projects []model.Project, err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := fmt.Sprintf(`
|
q := fmt.Sprintf(`
|
||||||
SELECT id, title, created_at, updated_at FROM projects
|
SELECT id, title, created_at, updated_at FROM publications
|
||||||
WHERE %s
|
WHERE %s
|
||||||
`, strings.Join(c, " OR "))
|
`, strings.Join(c, " OR "))
|
||||||
|
|
||||||
log := repo.log.With(slog.String("query", q))
|
log := repo.log.With(slog.String("query", q))
|
||||||
log.DebugContext(repo.ctx, "Getting projects by IDs")
|
log.DebugContext(repo.ctx, "Getting publications by IDs")
|
||||||
|
|
||||||
rows, err := tx.QueryContext(repo.ctx, q)
|
rows, err := tx.QueryContext(repo.ctx, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to get projects by IDs", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to get publications by IDs", slog.String("error", err.Error()))
|
||||||
return nil, errors.Join(ErrExecuteQuery, err)
|
return nil, errors.Join(ErrExecuteQuery, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ func (repo Project) GetByIDs(ids []uuid.UUID) (projects []model.Project, err err
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ps := []model.Project{}
|
ps := []model.Publication{}
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id uuid.UUID
|
var id uuid.UUID
|
||||||
@@ -176,23 +176,23 @@ func (repo Project) GetByIDs(ids []uuid.UUID) (projects []model.Project, err err
|
|||||||
|
|
||||||
err := rows.Scan(&id, &title, &dateCreatedStr, &dateUpdatedStr)
|
err := rows.Scan(&id, &title, &dateCreatedStr, &dateUpdatedStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to scan projects with IDs", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
||||||
return nil, errors.Join(ErrInvalidOutput, err)
|
return nil, errors.Join(ErrInvalidOutput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dateCreated, err := time.Parse(dateFormat, dateCreatedStr)
|
dateCreated, err := time.Parse(dateFormat, dateCreatedStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to scan projects with IDs", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
||||||
return nil, errors.Join(ErrInvalidOutput, err)
|
return nil, errors.Join(ErrInvalidOutput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dateUpdated, err := time.Parse(dateFormat, dateUpdatedStr)
|
dateUpdated, err := time.Parse(dateFormat, dateUpdatedStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to scan projects with IDs", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to scan publications with IDs", slog.String("error", err.Error()))
|
||||||
return nil, errors.Join(ErrInvalidOutput, err)
|
return nil, errors.Join(ErrInvalidOutput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ps = append(ps, model.Project{
|
ps = append(ps, model.Publication{
|
||||||
ID: id,
|
ID: id,
|
||||||
Title: title,
|
Title: title,
|
||||||
DateCreated: dateCreated,
|
DateCreated: dateCreated,
|
||||||
@@ -208,7 +208,7 @@ func (repo Project) GetByIDs(ids []uuid.UUID) (projects []model.Project, err err
|
|||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo Project) Update(p model.Project) error {
|
func (repo Publication) Update(p model.Publication) error {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
@@ -223,14 +223,14 @@ func (repo Project) Update(p model.Project) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := `
|
q := `
|
||||||
UPDATE projects
|
UPDATE publications
|
||||||
SET title = :title
|
SET title = :title
|
||||||
updated_at = :updated_at
|
updated_at = :updated_at
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
`
|
`
|
||||||
|
|
||||||
log := repo.log.With(slog.String("id", p.ID.String()), slog.String("query", q))
|
log := repo.log.With(slog.String("id", p.ID.String()), slog.String("query", q))
|
||||||
log.DebugContext(repo.ctx, "Updating project")
|
log.DebugContext(repo.ctx, "Updating publication")
|
||||||
|
|
||||||
_, err = tx.ExecContext(repo.ctx, q,
|
_, err = tx.ExecContext(repo.ctx, q,
|
||||||
sql.Named("title", p.Title),
|
sql.Named("title", p.Title),
|
||||||
@@ -238,7 +238,7 @@ func (repo Project) Update(p model.Project) error {
|
|||||||
sql.Named("id", p.ID),
|
sql.Named("id", p.ID),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to insert project", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to insert publication", slog.String("error", err.Error()))
|
||||||
return errors.Join(ErrExecuteQuery, err)
|
return errors.Join(ErrExecuteQuery, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ func (repo Project) Update(p model.Project) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo Project) DeleteByID(id uuid.UUID) error {
|
func (repo Publication) DeleteByID(id uuid.UUID) error {
|
||||||
repo.assert.NotNil(repo.db)
|
repo.assert.NotNil(repo.db)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
repo.assert.NotNil(repo.ctx)
|
repo.assert.NotNil(repo.ctx)
|
||||||
@@ -261,15 +261,15 @@ func (repo Project) DeleteByID(id uuid.UUID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := `
|
q := `
|
||||||
DELETE FROM projects WHERE id = :id
|
DELETE FROM publications WHERE id = :id
|
||||||
`
|
`
|
||||||
|
|
||||||
log := repo.log.With(slog.String("id", id.String()), slog.String("query", q))
|
log := repo.log.With(slog.String("id", id.String()), slog.String("query", q))
|
||||||
log.DebugContext(repo.ctx, "Deleting project")
|
log.DebugContext(repo.ctx, "Deleting publication")
|
||||||
|
|
||||||
_, err = tx.ExecContext(repo.ctx, q, sql.Named("id", id))
|
_, err = tx.ExecContext(repo.ctx, q, sql.Named("id", id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ErrorContext(repo.ctx, "Failed to delete project", slog.String("error", err.Error()))
|
log.ErrorContext(repo.ctx, "Failed to delete publication", slog.String("error", err.Error()))
|
||||||
return errors.Join(ErrExecuteQuery, err)
|
return errors.Join(ErrExecuteQuery, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ func newBaseRepostiory(ctx context.Context, db *sql.DB, log *slog.Logger, assert
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// TODO: Change all ErrDatabaseConn to ErrCloseConn
|
// TODO: Change all ErrDatabaseConn to ErrCloseConn
|
||||||
|
// TODO: Change error to be agnostic to underlying storage type
|
||||||
ErrDatabaseConn = errors.New("repository: failed to begin transaction/connection with database")
|
ErrDatabaseConn = errors.New("repository: failed to begin transaction/connection with database")
|
||||||
ErrCloseConn = errors.New("repository: failed to close/commit connection")
|
ErrCloseConn = errors.New("repository: failed to close/commit connection")
|
||||||
ErrExecuteQuery = errors.New("repository: failed to execute query")
|
ErrExecuteQuery = errors.New("repository: failed to execute query")
|
||||||
|
|||||||
@@ -14,27 +14,27 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type projectController struct {
|
type publicationController struct {
|
||||||
projectSvc *service.Project
|
publicationSvc *service.Publication
|
||||||
|
|
||||||
templates templates.ITemplate
|
templates templates.ITemplate
|
||||||
|
|
||||||
assert tinyssert.Assertions
|
assert tinyssert.Assertions
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProjectController(
|
func newPublicationController(
|
||||||
projectService *service.Project,
|
publicationService *service.Publication,
|
||||||
templates templates.ITemplate,
|
templates templates.ITemplate,
|
||||||
assertions tinyssert.Assertions,
|
assertions tinyssert.Assertions,
|
||||||
) *projectController {
|
) *publicationController {
|
||||||
return &projectController{
|
return &publicationController{
|
||||||
projectSvc: projectService,
|
publicationSvc: publicationService,
|
||||||
templates: templates,
|
templates: templates,
|
||||||
assert: assertions,
|
assert: assertions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctrl projectController) dashboard(w http.ResponseWriter, r *http.Request) {
|
func (ctrl publicationController) dashboard(w http.ResponseWriter, r *http.Request) {
|
||||||
userCtx := NewUserContext(r.Context())
|
userCtx := NewUserContext(r.Context())
|
||||||
|
|
||||||
userID, ok := userCtx.GetUserID()
|
userID, ok := userCtx.GetUserID()
|
||||||
@@ -43,7 +43,7 @@ func (ctrl projectController) dashboard(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
projects, err := ctrl.projectSvc.GetUserProjects(userID)
|
publications, err := ctrl.publicationSvc.ListOwnedBy(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
problem.NewInternalServerError(err).ServeHTTP(w, r)
|
problem.NewInternalServerError(err).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@@ -52,15 +52,15 @@ func (ctrl projectController) dashboard(w http.ResponseWriter, r *http.Request)
|
|||||||
ps := make([]struct {
|
ps := make([]struct {
|
||||||
ID string
|
ID string
|
||||||
Title string
|
Title string
|
||||||
}, len(projects))
|
}, len(publications))
|
||||||
|
|
||||||
for i, project := range projects {
|
for i, publication := range publications {
|
||||||
ps[i] = struct {
|
ps[i] = struct {
|
||||||
ID string
|
ID string
|
||||||
Title string
|
Title string
|
||||||
}{
|
}{
|
||||||
ID: base64.URLEncoding.EncodeToString([]byte(project.ID.String())),
|
ID: base64.URLEncoding.EncodeToString([]byte(publication.ID.String())),
|
||||||
Title: project.Title,
|
Title: publication.Title,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,24 +70,24 @@ func (ctrl projectController) dashboard(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctrl projectController) getProject(w http.ResponseWriter, r *http.Request) {
|
func (ctrl publicationController) getPublication(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Handle private projects
|
// TODO: Handle private publications
|
||||||
|
|
||||||
shortProjectID := r.PathValue("projectID")
|
shortPublicationID := r.PathValue("publicationID")
|
||||||
|
|
||||||
id, err := base64.URLEncoding.DecodeString(shortProjectID)
|
id, err := base64.URLEncoding.DecodeString(shortPublicationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
problem.NewBadRequest(fmt.Sprintf("Incorrectly encoded project ID: %s", err.Error())).ServeHTTP(w, r)
|
problem.NewBadRequest(fmt.Sprintf("Incorrectly encoded publication ID: %s", err.Error())).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
projectID, err := uuid.ParseBytes(id)
|
publicationID, err := uuid.ParseBytes(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
problem.NewBadRequest("Project ID is not a valid UUID").ServeHTTP(w, r)
|
problem.NewBadRequest("Publication ID is not a valid UUID").ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
project, err := ctrl.projectSvc.GetProject(projectID)
|
publication, err := ctrl.publicationSvc.Get(publicationID)
|
||||||
if errors.Is(err, service.ErrNotFound) {
|
if errors.Is(err, service.ErrNotFound) {
|
||||||
problem.NewNotFound().ServeHTTP(w, r)
|
problem.NewNotFound().ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
@@ -96,8 +96,8 @@ func (ctrl projectController) getProject(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Return project template
|
// TODO: Return publication template
|
||||||
b, err := json.Marshal(project)
|
b, err := json.Marshal(publication)
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
if _, err := w.Write(b); err != nil {
|
if _, err := w.Write(b); err != nil {
|
||||||
@@ -106,7 +106,7 @@ func (ctrl projectController) getProject(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctrl projectController) createProject(w http.ResponseWriter, r *http.Request) {
|
func (ctrl publicationController) createPublication(w http.ResponseWriter, r *http.Request) {
|
||||||
userCtx := NewUserContext(r.Context())
|
userCtx := NewUserContext(r.Context())
|
||||||
|
|
||||||
userID, ok := userCtx.GetUserID()
|
userID, ok := userCtx.GetUserID()
|
||||||
@@ -121,12 +121,12 @@ func (ctrl projectController) createProject(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
project, err := ctrl.projectSvc.Create(title, userID)
|
publication, err := ctrl.publicationSvc.Create(title, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
problem.NewInternalServerError(err).ServeHTTP(w, r)
|
problem.NewInternalServerError(err).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path := fmt.Sprintf("/p/%s/", base64.URLEncoding.EncodeToString([]byte(project.ID.String())))
|
path := fmt.Sprintf("/publication/%s/", base64.URLEncoding.EncodeToString([]byte(publication.ID.String())))
|
||||||
http.Redirect(w, r, path, http.StatusSeeOther)
|
http.Redirect(w, r, path, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.capytal.cc/capytal/comicverse/service"
|
"code.capytal.cc/capytal/comicverse/service"
|
||||||
"code.capytal.cc/capytal/comicverse/templates"
|
"code.capytal.cc/capytal/comicverse/templates"
|
||||||
@@ -19,7 +18,7 @@ import (
|
|||||||
type router struct {
|
type router struct {
|
||||||
userService *service.User
|
userService *service.User
|
||||||
tokenService *service.Token
|
tokenService *service.Token
|
||||||
projectService *service.Project
|
publicationService *service.Publication
|
||||||
|
|
||||||
templates templates.ITemplate
|
templates templates.ITemplate
|
||||||
assets fs.FS
|
assets fs.FS
|
||||||
@@ -36,8 +35,8 @@ func New(cfg Config) (http.Handler, error) {
|
|||||||
if cfg.TokenService == nil {
|
if cfg.TokenService == nil {
|
||||||
return nil, errors.New("token service is nil")
|
return nil, errors.New("token service is nil")
|
||||||
}
|
}
|
||||||
if cfg.ProjectService == nil {
|
if cfg.PublicationService == nil {
|
||||||
return nil, errors.New("project service is nil")
|
return nil, errors.New("publication service is nil")
|
||||||
}
|
}
|
||||||
if cfg.Templates == nil {
|
if cfg.Templates == nil {
|
||||||
return nil, errors.New("templates is nil")
|
return nil, errors.New("templates is nil")
|
||||||
@@ -55,7 +54,7 @@ func New(cfg Config) (http.Handler, error) {
|
|||||||
r := &router{
|
r := &router{
|
||||||
userService: cfg.UserService,
|
userService: cfg.UserService,
|
||||||
tokenService: cfg.TokenService,
|
tokenService: cfg.TokenService,
|
||||||
projectService: cfg.ProjectService,
|
publicationService: cfg.PublicationService,
|
||||||
|
|
||||||
templates: cfg.Templates,
|
templates: cfg.Templates,
|
||||||
assets: cfg.Assets,
|
assets: cfg.Assets,
|
||||||
@@ -71,7 +70,7 @@ func New(cfg Config) (http.Handler, error) {
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
UserService *service.User
|
UserService *service.User
|
||||||
TokenService *service.Token
|
TokenService *service.Token
|
||||||
ProjectService *service.Project
|
PublicationService *service.Publication
|
||||||
|
|
||||||
Templates templates.ITemplate
|
Templates templates.ITemplate
|
||||||
Assets fs.FS
|
Assets fs.FS
|
||||||
@@ -89,8 +88,16 @@ func (router *router) setup() http.Handler {
|
|||||||
|
|
||||||
log.Debug("Initializing router")
|
log.Debug("Initializing router")
|
||||||
|
|
||||||
|
mux := multiplexer.New()
|
||||||
|
mux = multiplexer.WithFormMethod(mux, "x-method")
|
||||||
|
mux = multiplexer.WithPatternRules(mux,
|
||||||
|
multiplexer.EnsureMethod(),
|
||||||
|
multiplexer.EnsureStrictEnd(),
|
||||||
|
multiplexer.EnsureTrailingSlash(),
|
||||||
|
)
|
||||||
|
|
||||||
r := smalltrip.NewRouter(
|
r := smalltrip.NewRouter(
|
||||||
smalltrip.WithAssertions(router.assert),
|
smalltrip.WithMultiplexer(mux),
|
||||||
smalltrip.WithLogger(log.WithGroup("smalltrip")),
|
smalltrip.WithLogger(log.WithGroup("smalltrip")),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,17 +121,17 @@ func (router *router) setup() http.Handler {
|
|||||||
Templates: router.templates,
|
Templates: router.templates,
|
||||||
Assert: router.assert,
|
Assert: router.assert,
|
||||||
})
|
})
|
||||||
projectController := newProjectController(router.projectService, router.templates, router.assert)
|
publicationController := newPublicationController(router.publicationService, router.templates, router.assert)
|
||||||
|
|
||||||
r.Handle("/assets/", http.StripPrefix("/assets/", http.FileServerFS(router.assets)))
|
r.Handle("GET /assets/{_file...}", http.StripPrefix("/assets/", http.FileServerFS(router.assets)))
|
||||||
|
|
||||||
r.Use(userController.userMiddleware)
|
r.Use(userController.userMiddleware)
|
||||||
|
|
||||||
r.HandleFunc("/{$}", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Add a way to the user to bypass this check and see the landing page.
|
// TODO: Add a way to the user to bypass this check and see the landing page.
|
||||||
// Probably a query parameter to bypass like "?landing=true"
|
// Probably a query parameter to bypass like "?landing=true"
|
||||||
if _, ok := NewUserContext(r.Context()).GetUserID(); ok {
|
if _, ok := NewUserContext(r.Context()).GetUserID(); ok {
|
||||||
projectController.dashboard(w, r)
|
publicationController.dashboard(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,23 +141,14 @@ func (router *router) setup() http.Handler {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
r.HandleFunc("/login/{$}", userController.login)
|
r.HandleFunc("GET /login/{$}", userController.login)
|
||||||
r.HandleFunc("/register/{$}", userController.register)
|
r.HandleFunc("POST /login/{$}", userController.login)
|
||||||
|
r.HandleFunc("GET /register/{$}", userController.register)
|
||||||
|
r.HandleFunc("POST /register/{$}", userController.register)
|
||||||
|
|
||||||
// TODO: Provide/redirect short project-id paths to long paths with the project title as URL /projects/title-of-the-project-<start of uuid>
|
// TODO: Provide/redirect short publication-id paths to long paths with the publication title as URL /publications/title-of-the-publication-<start of uuid>
|
||||||
r.HandleFunc("GET /p/{projectID}/{$}", projectController.getProject)
|
r.HandleFunc("GET /publication/{publicationID}/{$}", publicationController.getPublication)
|
||||||
r.HandleFunc("POST /p/{$}", projectController.createProject)
|
r.HandleFunc("POST /publication/{$}", publicationController.createPublication)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMethod is a helper function to get the HTTP method of request, tacking precedence
|
|
||||||
// the "x-method" argument sent by requests via form or query values.
|
|
||||||
func getMethod(r *http.Request) string {
|
|
||||||
m := r.FormValue("x-method")
|
|
||||||
if m != "" {
|
|
||||||
return strings.ToUpper(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.ToUpper(r.Method)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.capytal.cc/capytal/comicverse/model"
|
|
||||||
"code.capytal.cc/capytal/comicverse/repository"
|
|
||||||
"code.capytal.cc/loreddev/x/tinyssert"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Project struct {
|
|
||||||
projectRepo *repository.Project
|
|
||||||
permissionRepo *repository.Permissions
|
|
||||||
|
|
||||||
log *slog.Logger
|
|
||||||
assert tinyssert.Assertions
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProject(
|
|
||||||
project *repository.Project,
|
|
||||||
permissions *repository.Permissions,
|
|
||||||
logger *slog.Logger,
|
|
||||||
assertions tinyssert.Assertions,
|
|
||||||
) *Project {
|
|
||||||
return &Project{
|
|
||||||
projectRepo: project,
|
|
||||||
permissionRepo: permissions,
|
|
||||||
|
|
||||||
log: logger,
|
|
||||||
assert: assertions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc Project) Create(title string, ownerUserID ...uuid.UUID) (model.Project, error) {
|
|
||||||
log := svc.log.With(slog.String("title", title))
|
|
||||||
log.Info("Creating project")
|
|
||||||
defer log.Info("Finished creating project")
|
|
||||||
|
|
||||||
id, err := uuid.NewV7()
|
|
||||||
if err != nil {
|
|
||||||
return model.Project{}, fmt.Errorf("service: failed to generate id: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
p := model.Project{
|
|
||||||
ID: id,
|
|
||||||
Title: title,
|
|
||||||
DateCreated: now,
|
|
||||||
DateUpdated: now,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = svc.projectRepo.Create(p)
|
|
||||||
if err != nil {
|
|
||||||
return model.Project{}, fmt.Errorf("service: failed to create project: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ownerUserID) > 0 {
|
|
||||||
err := svc.SetAuthor(p.ID, ownerUserID[0])
|
|
||||||
if err != nil {
|
|
||||||
return model.Project{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc Project) SetAuthor(projectID uuid.UUID, userID uuid.UUID) error {
|
|
||||||
log := svc.log.With(slog.String("project", projectID.String()), slog.String("user", userID.String()))
|
|
||||||
log.Info("Setting project owner")
|
|
||||||
defer log.Info("Finished setting project owner")
|
|
||||||
|
|
||||||
if _, err := svc.permissionRepo.GetByID(projectID, userID); err == nil {
|
|
||||||
err := svc.permissionRepo.Update(projectID, userID, model.PermissionAuthor)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("service: failed to update project author: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p := model.PermissionAuthor
|
|
||||||
|
|
||||||
err := svc.permissionRepo.Create(projectID, userID, p)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("service: failed to set project owner: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc Project) GetUserProjects(userID uuid.UUID) ([]model.Project, error) {
|
|
||||||
perms, err := svc.permissionRepo.GetByUserID(userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("service: failed to get user permissions: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ids := []uuid.UUID{}
|
|
||||||
for project, permissions := range perms {
|
|
||||||
if permissions.Has(model.PermissionRead) {
|
|
||||||
ids = append(ids, project)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ids) == 0 {
|
|
||||||
return []model.Project{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
projects, err := svc.projectRepo.GetByIDs(ids)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("service: failed to get projects: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc Project) GetProject(projectID uuid.UUID) (model.Project, error) {
|
|
||||||
p, err := svc.projectRepo.GetByID(projectID)
|
|
||||||
if err != nil {
|
|
||||||
return model.Project{}, fmt.Errorf("service: failed to get project: %w", err)
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
124
service/publication.go
Normal file
124
service/publication.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.capytal.cc/capytal/comicverse/model"
|
||||||
|
"code.capytal.cc/capytal/comicverse/repository"
|
||||||
|
"code.capytal.cc/loreddev/x/tinyssert"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Publication struct {
|
||||||
|
publicationRepo *repository.Publication
|
||||||
|
permissionRepo *repository.Permissions
|
||||||
|
|
||||||
|
log *slog.Logger
|
||||||
|
assert tinyssert.Assertions
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPublication(
|
||||||
|
publication *repository.Publication,
|
||||||
|
permissions *repository.Permissions,
|
||||||
|
logger *slog.Logger,
|
||||||
|
assertions tinyssert.Assertions,
|
||||||
|
) *Publication {
|
||||||
|
return &Publication{
|
||||||
|
publicationRepo: publication,
|
||||||
|
permissionRepo: permissions,
|
||||||
|
|
||||||
|
log: logger,
|
||||||
|
assert: assertions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc Publication) Get(publicationID uuid.UUID) (model.Publication, error) {
|
||||||
|
p, err := svc.publicationRepo.GetByID(publicationID)
|
||||||
|
if err != nil {
|
||||||
|
return model.Publication{}, fmt.Errorf("service: failed to get publication: %w", err)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc Publication) Create(title string, ownerUserID ...uuid.UUID) (model.Publication, error) {
|
||||||
|
log := svc.log.With(slog.String("title", title))
|
||||||
|
log.Info("Creating publication")
|
||||||
|
defer log.Info("Finished creating publication")
|
||||||
|
|
||||||
|
id, err := uuid.NewV7()
|
||||||
|
if err != nil {
|
||||||
|
return model.Publication{}, fmt.Errorf("service: failed to generate id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
p := model.Publication{
|
||||||
|
ID: id,
|
||||||
|
Title: title,
|
||||||
|
DateCreated: now,
|
||||||
|
DateUpdated: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = svc.publicationRepo.Create(p)
|
||||||
|
if err != nil {
|
||||||
|
return model.Publication{}, fmt.Errorf("service: failed to create publication: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ownerUserID) > 0 {
|
||||||
|
err := svc.SetAuthor(p.ID, ownerUserID[0])
|
||||||
|
if err != nil {
|
||||||
|
return model.Publication{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc Publication) SetAuthor(publicationID uuid.UUID, userID uuid.UUID) error {
|
||||||
|
log := svc.log.With(slog.String("publication", publicationID.String()), slog.String("user", userID.String()))
|
||||||
|
log.Info("Setting publication owner")
|
||||||
|
defer log.Info("Finished setting publication owner")
|
||||||
|
|
||||||
|
if _, err := svc.permissionRepo.GetByID(publicationID, userID); err == nil {
|
||||||
|
err := svc.permissionRepo.Update(publicationID, userID, model.PermissionAuthor)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("service: failed to update publication author: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := model.PermissionAuthor
|
||||||
|
|
||||||
|
err := svc.permissionRepo.Create(publicationID, userID, p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("service: failed to set publication owner: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc Publication) ListOwnedBy(userID uuid.UUID) ([]model.Publication, error) {
|
||||||
|
perms, err := svc.permissionRepo.GetByUserID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("service: failed to get user permissions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := []uuid.UUID{}
|
||||||
|
for publication, permissions := range perms {
|
||||||
|
if permissions.Has(model.PermissionRead) {
|
||||||
|
ids = append(ids, publication)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return []model.Publication{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
publications, err := svc.publicationRepo.GetByIDs(ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("service: failed to get publications: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return publications, nil
|
||||||
|
}
|
||||||
@@ -4,12 +4,12 @@
|
|||||||
{{if and (ne . nil) (ne (len .) 0)}}
|
{{if and (ne . nil) (ne (len .) 0)}}
|
||||||
<section class="flex h-64 flex-col gap-5">
|
<section class="flex h-64 flex-col gap-5">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<h2 class="text-2xl">Projects</h2>
|
<h2 class="text-2xl">Publications</h2>
|
||||||
<form action="/p/" method="post">
|
<form action="/publication/" method="post">
|
||||||
<button
|
<button
|
||||||
class="rounded-full bg-slate-700 p-1 px-3 text-sm text-slate-100"
|
class="rounded-full bg-slate-700 p-1 px-3 text-sm text-slate-100"
|
||||||
>
|
>
|
||||||
New project
|
New publication
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,11 +20,11 @@
|
|||||||
<div class="w-38 grid h-full grid-rows-2 bg-slate-500">
|
<div class="w-38 grid h-full grid-rows-2 bg-slate-500">
|
||||||
<div class="bg-blue-500 p-2">Image</div>
|
<div class="bg-blue-500 p-2">Image</div>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<a href="/p/{{.ID}}/">
|
<a href="/publication/{{.ID}}/">
|
||||||
<h3>{{.Title}}</h3>
|
<h3>{{.Title}}</h3>
|
||||||
<p class="hidden">{{.ID}}</p>
|
<p class="hidden">{{.ID}}</p>
|
||||||
</a>
|
</a>
|
||||||
<form action="/p/{{.ID}}/" method="post">
|
<form action="/publication/{{.ID}}/" method="post">
|
||||||
<input type="hidden" name="x-method" value="delete" />
|
<input type="hidden" name="x-method" value="delete" />
|
||||||
<button
|
<button
|
||||||
class="rounded-full bg-red-700 p-1 px-3 text-sm text-slate-100"
|
class="rounded-full bg-red-700 p-1 px-3 text-sm text-slate-100"
|
||||||
@@ -41,16 +41,21 @@
|
|||||||
<div
|
<div
|
||||||
class="fixed flex h-screen w-full items-center justify-center top-0 left-0"
|
class="fixed flex h-screen w-full items-center justify-center top-0 left-0"
|
||||||
>
|
>
|
||||||
<form action="/p/" method="post" class="bg-slate-300 rounded-full">
|
<form
|
||||||
|
action="/publication/"
|
||||||
|
method="post"
|
||||||
|
class="bg-slate-300 rounded-full"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="title"
|
name="title"
|
||||||
placeholder="Project title"
|
placeholder="Publication title"
|
||||||
|
value="{{randomName}}"
|
||||||
required
|
required
|
||||||
class="pl-5"
|
class="pl-5"
|
||||||
/>
|
/>
|
||||||
<button class="rounded-full bg-slate-700 p-2 px-5 text-slate-100">
|
<button class="rounded-full bg-slate-700 p-2 px-5 text-slate-100">
|
||||||
New project
|
New publication
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
{{define "project"}}
|
|
||||||
{{template "layout-page-start" (args "Title" .Title)}}
|
|
||||||
<div class="fixed w-full h-full bg-green-500 grid grid-cols-4 grid-rows-1">
|
|
||||||
<nav class="bg-red-500 h-full">
|
|
||||||
<h1>{{.Title}}</h1>
|
|
||||||
<p>{{.ID}}</p>
|
|
||||||
</nav>
|
|
||||||
<main class="overflow-y-scroll flex justify-center col-span-3 py-20">
|
|
||||||
<div class="flex flex-col gap-10 h-fit">
|
|
||||||
{{range $page := .Pages}}
|
|
||||||
<section id="{{$page.ID}}" class="w-fit">
|
|
||||||
<!--
|
|
||||||
INFO: The interaction form could be another page that is shown
|
|
||||||
when "Add Interaction" is clicked. Said page could be also a partial
|
|
||||||
than can replace the current image using htmx, so it is
|
|
||||||
compatible with JavaScript enabled or not.
|
|
||||||
-->
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<form action="/projects/{{$.ID}}/pages/{{$page.ID}}/interactions/" method="post" class="w-100">
|
|
||||||
<div class="flex">
|
|
||||||
{{if (gt (len $page.Interactions) 0)}}
|
|
||||||
<div class="relative flex">
|
|
||||||
<div class="absolute z-2 w-full h-full top-0 left-0">
|
|
||||||
{{range $interactionID, $interaction := $page.Interactions}}
|
|
||||||
<a class="absolute" href="{{$interaction.URL}}"
|
|
||||||
style="top:{{$interaction.Y}}%;left:{{$interaction.X}}%;">
|
|
||||||
<span
|
|
||||||
class="bg-red-200 opacity-10 block w-10 h-10 transform -translate-x-[50%] -translate-y-[50%]"></span>
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
<img src="/projects/{{$.ID}}/pages/{{$page.ID}}/" class="z-1 relative">
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<img src="/projects/{{$.ID}}/pages/{{$page.ID}}/" class="z-1 relative">
|
|
||||||
{{end}}
|
|
||||||
<input type="range" min="0" max="100" name="y" style="writing-mode: vertical-lr;">
|
|
||||||
</div>
|
|
||||||
<input type="range" min="0" max="100" name="x" class="w-full">
|
|
||||||
<input type="url" required name="link" class="bg-slate-300" placeholder="url of interaction">
|
|
||||||
<button class="rounded-full bg-blue-700 p-1 px-3 text-sm text-slate-100">
|
|
||||||
Add interaction
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{{if (gt (len $page.Interactions) 0)}}
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
{{range $interactionID, $interaction := $page.Interactions}}
|
|
||||||
<form action="/projects/{{$.ID}}/pages/{{$page.ID}}/interactions/{{$interactionID}}/"
|
|
||||||
method="post">
|
|
||||||
<input type="hidden" name="x-method" value="delete">
|
|
||||||
<button class="rounded-full bg-red-700 p-1 px-3 text-sm text-slate-100">
|
|
||||||
🗑️{{$interaction.URL}}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
<form action="/projects/{{$.ID}}/pages/{{$page.ID}}/" method="post">
|
|
||||||
<input type="hidden" name="x-method" value="delete">
|
|
||||||
<button class="rounded-full bg-red-700 p-1 px-3 text-sm text-slate-100">
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
{{end}}
|
|
||||||
<form action="/projects/{{.ID}}/pages/" method="post" enctype="multipart/form-data">
|
|
||||||
<input type="file" name="image" required>
|
|
||||||
<button>Add new page</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
{{template "layout-page-end"}}
|
|
||||||
{{end}}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{{define "projects"}} {{end}}
|
|
||||||
111
templates/publication.html
Normal file
111
templates/publication.html
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
{{define "publication"}} {{template "layout-page-start" (args "Title" .Title)}}
|
||||||
|
<div class="fixed w-full h-full bg-green-500 grid grid-cols-4 grid-rows-1">
|
||||||
|
<nav class="bg-red-500 h-full">
|
||||||
|
<h1>{{.Title}}</h1>
|
||||||
|
<p>{{.ID}}</p>
|
||||||
|
</nav>
|
||||||
|
<main class="overflow-y-scroll flex justify-center col-span-3 py-20">
|
||||||
|
<div class="flex flex-col gap-10 h-fit">
|
||||||
|
{{range $page := .Pages}}
|
||||||
|
<section id="{{$page.ID}}" class="w-fit">
|
||||||
|
<!--
|
||||||
|
INFO: The interaction form could be another page that is shown
|
||||||
|
when "Add Interaction" is clicked. Said page could be also a partial
|
||||||
|
than can replace the current image using htmx, so it is
|
||||||
|
compatible with JavaScript enabled or not.
|
||||||
|
-->
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<form
|
||||||
|
action="/publications/{{$.ID}}/pages/{{$page.ID}}/interactions/"
|
||||||
|
method="post"
|
||||||
|
class="w-100"
|
||||||
|
>
|
||||||
|
<div class="flex">
|
||||||
|
{{if (gt (len $page.Interactions) 0)}}
|
||||||
|
<div class="relative flex">
|
||||||
|
<div class="absolute z-2 w-full h-full top-0 left-0">
|
||||||
|
{{range $interactionID, $interaction := $page.Interactions}}
|
||||||
|
<a
|
||||||
|
class="absolute"
|
||||||
|
href="{{$interaction.URL}}"
|
||||||
|
style="top:{{$interaction.Y}}%;left:{{$interaction.X}}%;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="bg-red-200 opacity-10 block w-10 h-10 transform -translate-x-[50%] -translate-y-[50%]"
|
||||||
|
></span>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
src="/publications/{{$.ID}}/pages/{{$page.ID}}/"
|
||||||
|
class="z-1 relative"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<img
|
||||||
|
src="/publications/{{$.ID}}/pages/{{$page.ID}}/"
|
||||||
|
class="z-1 relative"
|
||||||
|
/>
|
||||||
|
{{end}}
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
name="y"
|
||||||
|
style="writing-mode: vertical-lr"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<input type="range" min="0" max="100" name="x" class="w-full" />
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
required
|
||||||
|
name="link"
|
||||||
|
class="bg-slate-300"
|
||||||
|
placeholder="url of interaction"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="rounded-full bg-blue-700 p-1 px-3 text-sm text-slate-100"
|
||||||
|
>
|
||||||
|
Add interaction
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{{if (gt (len $page.Interactions) 0)}}
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{{range $interactionID, $interaction := $page.Interactions}}
|
||||||
|
<form
|
||||||
|
action="/publications/{{$.ID}}/pages/{{$page.ID}}/interactions/{{$interactionID}}/"
|
||||||
|
method="post"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="x-method" value="delete" />
|
||||||
|
<button
|
||||||
|
class="rounded-full bg-red-700 p-1 px-3 text-sm text-slate-100"
|
||||||
|
>
|
||||||
|
🗑️{{$interaction.URL}}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<form action="/publications/{{$.ID}}/pages/{{$page.ID}}/" method="post">
|
||||||
|
<input type="hidden" name="x-method" value="delete" />
|
||||||
|
<button
|
||||||
|
class="rounded-full bg-red-700 p-1 px-3 text-sm text-slate-100"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{{end}}
|
||||||
|
<form
|
||||||
|
action="/publications/{{.ID}}/pages/"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
>
|
||||||
|
<input type="file" name="image" required />
|
||||||
|
<button>Add new page</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
{{template "layout-page-end"}} {{end}}
|
||||||
1
templates/publications.html
Normal file
1
templates/publications.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{{define "publications"}} {{end}}
|
||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
|
||||||
|
"code.capytal.cc/capytal/comicverse/internals/randname"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -32,6 +34,9 @@ var (
|
|||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
},
|
},
|
||||||
|
"randomName": func() string {
|
||||||
|
return randname.New()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user